Java 中线程的 6 种状态详解 (NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)
java.lang.Thread.State 枚举类中定义了六种线程的状态,可以调用线程 Thread 中的 getState()方法获取当前线程的状态。
具体状态切换如下图所示,下图源自《Java 并发编程艺术》
📌由图 4-1 中可以看到,线程创建之后,调用 start()方法开始运行。当线程执行 wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行 Runnable 的 run()方法之后将会进入到终止状态。⚠️注意:Java 将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入 synchronized 关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在 java.concurrent 包中 Lock 接口的线程状态却是等待状态,因为 java.concurrent 包中 Lock 接口对于阻塞的实现均使用了 LockSupport 类中的相关方法。
新建状态(NEW)
即用 new 关键字新建一个线程,这个线程就处于新建状态。
运行状态(RUNNABLE)
操作系统中的就绪和运行两种状态,在 Java 中统称为 RUNNABLE。
就绪状态(READY)
当线程对象调用了 start()方法之后,线程处于就绪状态,就绪意味着该线程可以执行,但具体啥时候执行将取决于 JVM 里线程调度器的调度。
不允许对一个线程多次使用 start。
线程执行完成之后,不能试图用 start 将其唤醒。
其他状态 ->就绪线程调用 start(),新建状态转化为就绪状态。线程 sleep(long)时间到,等待状态转化为就绪状态。阻塞式 IO 操作结果返回,线程变为就绪状态。其他线程调用 join()方法,结束之后转化为就绪状态。线程对象拿到对象锁之后,也会进入就绪状态。
运行状态(RUNNING)
处于就绪状态的线程获得了 CPU 之后,真正开始执行 run()方法的线程执行体时,意味着该线程就已经处于运行状态。需要注意的是,对于单处理器,一个时刻只能有一个线程处于运行状态。对于抢占式策略的系统来说,系统会给每个线程一小段时间处理各自的任务。时间用完之后,系统负责夺回线程占用的资源。下一段时间里,系统会根据一定规则,再次进行调度。运行状态转变为就绪状态的情形:线程失去处理器资源。线程不一定完整执行的,执行到一半,说不定就被别的线程抢走了。调用 yield()静态方法,暂时暂停当前线程,让系统的线程调度器重新调度一次,它自己完全有可能再次运行。yield 方法的官方解释:A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.提示调度程序,当前线程愿意放弃当前对处理器的使用。这时,当前线程将会被置为就绪状态,和其他线程一样等待调度,这时候根据不同优先级决定的概率,当前线程完全有可能再次抢到处理器资源。
阻塞状态(BLOCKED)
阻塞状态表示线程正等待监视器锁,而陷入的状态。以下场景线程将会阻塞:
当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。
synchronized:线程等待进入 synchronized 同步方法。线程等待进入 synchronized 同步代码块。
Lock:线程等待进入 Lock 同步段。
线程取得锁,就会从阻塞状态转变为就绪状态。
等待状态(WAITING)
进入该状态表示当前线程需要等待其他线程做出一些的特定的动作(通知或中断)。运行->等待
当前线程运行过程中,其他线程调用 join 方法,当前线程将会进入等待状态。
当前线程对象调用 wait()方法。-LockSupport.park():出于线程调度的目的禁用当前线程。
等待->就绪
等待的线程被其他线程对象唤醒,notify()和 notifyAll()
LockSupport.unpark(Thread),与上面 park 方法对应,给出许可证,解除等待状态。
超时等待状态(TIMED_WAITING)
区别于 WAITING,它可以在指定的时间自行返回。运行->超时等待
调用静态方法,Thread.sleep(long)
线程对象调用 wait(long)方法
其他线程调用指定时间的 join(long)。
LockSupport.parkNanos()。
LockSupport.parkUntil()。
补充:sleep 和 yield 的不同之处:sleep(long)方法会使线程转入超时等待状态,时间到了之后才会转入就绪状态。而 yield()方法不会将线程转入等待,而是强制线程进入就绪状态。使用 sleep(long)方法需要处理异常,而 yield()不用。超时等待->就绪
同样的,等待的线程被其他线程对象唤醒,notify()和 notifyAll()。
LockSupport.unpark(Thread)。
消亡状态
即线程的终止,表示线程已经执行完毕。前面已经说了,已经消亡的线程不能通过 start 再次唤醒。
run()和 call()线程执行体中顺利执行完毕,线程正常终止。
线程抛出一个没有捕获的 Exception 或 Error。
💡需要注意的是:主线成和子线程互不影响,子线程并不会因为主线程结束就结束。
参考:《Java 并发编程的艺术》
本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
保持热爱,奔赴下一场山海。🏃🏃🏃
版权声明: 本文为 InfoQ 作者【共饮一杯无】的原创文章。
原文链接:【http://xie.infoq.cn/article/873a38a88ac00594a1998c965】。文章转载请联系作者。
评论