Java线程中的常见方法(start方法和run方法)

 更新时间:2022年07月29日 16:10:02   作者:0x3f3f3f3f  
这篇文章主要介绍了Java线程中的常见方法(start方法和run方法),文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

start方法和run方法

$start()$方法用来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到$cpu$时间片,就开始执行$run()$方法。而直接调用$run()$方法,仅仅只是调用了一个类里的方法,其本质上还是在当前线程中执行的,因此只有使用$start()$方法来调用$run()$方法才能实现真正的多线程。

示例代码

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t1.run();
    }
}

上述代码是直接调用的$run()$方法。可以看到打印信息里,是$main$线程执行了这个方法。

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t1.start();
    }
}

而如果使用$start()$方法启动,才是真正的由$t1$线程执行的$run$方法。

注意

需要注意的是,当$Thread$对象调用了$start()$方法后,就会进入就绪状态,处于就绪状态时无法再调用$start()$方法,否则就会抛出$IllegalThreadStateException$异常,如下代码所示

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t1.start();
        t1.start();
    }
}

异常信息:

sleep方法与yield方法

sleep

  • 调用$sleep()$方法会让当前线程从$Running$状态变成$Time Waiting$状态(阻塞)
  • 其它线程可以使用$interrupt$方法打断正在睡眠的线程,此时$sleep$方法会抛出InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用$TimeUnit$的$sleep$代替$Thread$的$sleep$来获得更好的可读性示例代码
@Slf4j(topic = "c.Test5")
public class Test5 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        log.debug("t1 state {}", t1.getState());
        //让主线程休眠500ms
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
    }
}
//17:13:21.729 [main] DEBUG c.Test5 - t1 state RUNNABLE
//17:13:22.245 [main] DEBUG c.Test5 - t1 state TIMED_WAITING

上述代码中,首先启动$t1$线程,此时打印线程的状态应该是处于$RUNNABLE$状态,而让主线程休眠是防止主线程先执行打印,但是还未进入到$sleep()$状态。当执行到$run()$里边的$sleep$方法时,线程进入$TIMED WAITING$状态

@Slf4j(topic = "c.Test6")
public class Thread6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("enter sleep");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt t1");
        //被唤醒
        t1.interrupt();
    }
}

执行结果

上述代码中,当$start$方法启动后,$t1$线程进入睡眠状态,打印提示信息,睡眠时间为$2s$,在$main$线程中睡眠$1s$后打断$t1$线程的睡眠,提示打断信息,并且调用$interrupt()$方法,此时线程被打断,抛出异常。

$TimeUnit$类中新增了以什么单位去睡眠,可读性更好,但是本质上没区别,只是进行了单位换算

TimeUnit.SECONDS.sleep(1);//该语句作用是睡眠一秒

yield

调用$yield$会让当前进程从$Running$进入到$Runnable$就绪状态,然后调度执行其他线程具体的实现依赖于操作系统的任务调度器,(即当任务调度器中没有其他任务时,即使让出$cpu$,也会继续执行该线程)$sleep$执行后是进入阻塞状态,此时睡眠时间不结束,就不会分配$cpu$给该线程,但是$yield$是进入就绪状态,即如果没有其他线程需要执行,那么还会给该线程分配时间片,这是$sleep$和$yield$的最大区别线程优先级

线程优先级

会提示调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略他
如果$cpu$比较忙,那么优先级高的会获得更多的时间片,可$cpu$空闲时,优先级几乎没有

sleep的应用-防止cpu占用100%

在没有利用$cpu$来计算时,不要让$while(true)$空转浪费$cpu$,这时可以可以使用$yield$或者$sleep$来让$cpu$的使用权交给其他程序

while (true) {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
  }
}

可以使用$wait$或者条件变量达到类似的效果
不同的是后两者都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
$sleep$适用于无需锁同步的场景

join方法

以下程序的打印结果:

@Slf4j(topic = "c.Test6")
public class Test6 {
    static int r = 0;
    public static void main(String[] args) {
        test();
    }
    private static void test() {
        log.debug("开始");
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                log.debug("开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("结束");
                r = 10;
            }
        };
        t.start();
        log.debug("r的值是{}", r);
        log.debug("结束");
    }
}

因为主线程和$t1$线程是并行的,$t1$线程需要$1s$后才能计算出$r$的值,而主线程一开始就要打印出$r$的值,因此打印的值为0

解决方法:

在$t.start();$后边加上$t.join();$即可。$join$的作用是等待某线程运行结束。
以调用方的角度来说,需要等待结果返回才能继续执行就是同步,不需要等待返回结果就能继续执行的就是异步。

因此$join$方法实际上是让其同步执行

有实效的等待

$join(毫秒)$方法里可以有一个参数是传入等待的时间,如果线程执行时间大于等待时间,则等待时间到了之后,就会停止等待。如果线程执行时间小于等待时间,则线程执行完毕之后,等待也会跟着结束。不会把设置的等待时间过完。

interrupt方法

打断$sleep, wait, join$的线程,即打断阻塞状态的线程
打断$sleep$的线程,会清空打断状态

@Slf4j(topic = "c.Test7")
public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                log.debug("sleep...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t.interrupt();
        log.debug("打断标记: {}", t.isInterrupted());
    }
}

打断正常运行的线程,不会清空打断状态

因此我们可以在线程中判断打断标记,来决定是否被打断,以及执行被打断之前的收尾工作。

@Slf4j(topic = "c.Test8")
public class Test8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        log.debug("线程被打断了");
                        break;
                    }
                }
            }
        };
        t.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t.interrupt();
    }
}

守护线程

默认情况下,$java$需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完毕,也会强制结束。

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                while (true) {

                }
            }
        };
        //设置线程为守护线程
        t.setDaemon(true);
        t.start();
        Thread.sleep(1000);
        log.debug("主线程结束");
    }
}

如果不把$t$设置为守护线程,则因为线程内部的死循环,导致程序不会结束运行。

到此这篇关于Java线程中的常见方法(start方法和run方法)的文章就介绍到这了,更多相关Java线程方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MyBatis分页查询返回list的时候出现null的问题

    MyBatis分页查询返回list的时候出现null的问题

    这篇文章主要介绍了MyBatis分页查询返回list的时候出现null的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Java中Lambda表达式的使用详细教程

    Java中Lambda表达式的使用详细教程

    这篇文章将通过示例为大家详细展示Java中Lambda表达式的用法,同时也会介绍Lambda的相关知识,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-05-05
  • springboot 基于Tomcat容器的自启动流程分析

    springboot 基于Tomcat容器的自启动流程分析

    这篇文章主要介绍了springboot 基于Tomcat容器的自启动流程分析,Spring通过注解导入Bean大体可分为四种方式,我们主要来说Import的两种实现方法,需要的朋友可以参考下
    2020-02-02
  • 详解Java抽象类与普通类的区别

    详解Java抽象类与普通类的区别

    今天给大家带来的是关于Java的相关知识,文章围绕着Java抽象类与普通类的区别展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • 深入浅出RocketMQ的事务消息

    深入浅出RocketMQ的事务消息

    RocketMQ事务消息(Transactional Message)是指应用本地事务和发送消息操作可以被定义到全局事务中,要么同时成功,要么同时失败。本文主要介绍了RocketMQ事务消息的相关知识,需要的可以参考一下
    2023-04-04
  • @TransactionalEventListener的使用和实现原理分析

    @TransactionalEventListener的使用和实现原理分析

    这篇文章主要介绍了@TransactionalEventListener的使用和实现原理分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 解决springboot整合cxf启动报错,原因是版本问题

    解决springboot整合cxf启动报错,原因是版本问题

    这篇文章主要介绍了解决springboot整合cxf启动报错,原因是版本问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 浅谈Action+Service +Dao 功能

    浅谈Action+Service +Dao 功能

    下面小编就为大家带来一篇浅谈Action+Service +Dao 功能。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Java实现XML文件学生通讯录

    Java实现XML文件学生通讯录

    这篇文章主要为大家详细介绍了Java实现XML文件学生通讯录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • java异常和错误类总结(必看篇)

    java异常和错误类总结(必看篇)

    下面小编就为大家带来一篇java异常和错误类总结(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-09-09

最新评论