文章目录
一、线程状态概述
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中, 有几种状态呢?在API中 java.lang.Thread.State 这个枚举中给出了六种线程状态:
这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析 。
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed_Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
二、线程的状态图
三、状态详细说明
3.1 初始状态(NEW)
New 表示线程被创建但尚未启动的状态:当我们用 new Thread()
新建一个线程时,如果线程没有开始运行 start()
方法,所以也没有开始执行 run()
方法里面的代码,那么此时它的状态就是 New。而一旦线程调用了 start()
,它的状态就会从 New 变成 Runnable
3.2 可运行状态(RUNNABLE)
Java 中的 Runable
状态对应操作系统线程状态中的两种状态,分别是 Ready
和Running
,也就是说,Java 中处于 Runnable
状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
注意:
如果一个正在运行的线程是
Runnable
状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是Runnable
,因为它有可能随时被调度回来继续执行任务。
3.2.1 就绪状态(RUNNABLE之READY)
- 就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
- 调用线程的
start()
方法,此线程进入就绪状态。 - 当前线程
sleep()
方法结束,其他线程join()
结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。 - 当前线程时间片用完了,调用当前线程的
yield()
方法,当前线程进入就绪状态。 - 锁池里的线程拿到对象锁后,进入就绪状态。
3.2.2 运行中状态(RUNNABLE之RUNNING)
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一的一种方式。
3.3 阻塞状态
在 Java 中阻塞状态通常不仅仅是 Blocked,实际上它包括三种状态,分别是 Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待),这三种状态统称为阻塞状态。
3.3.1 Blocked(被阻塞)
从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized
保护的代码时没有抢到 monitor 锁,无论是进入 synchronized
代码块,还是 synchronized
方法,都是一样。
3.3.2 Waiting(等待)
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
线程进入 Waiting 状态有三种可能性:
- 没有设置 Timeout 参数的
Object.wait()
方法。 - 没有设置 Timeout 参数的
Thread.join()
方法。 LockSupport.park()
方法。
刚才强调过,Blocked 仅仅针对 synchronized monitor
锁,可是在 Java 中还有很多其他的锁,比如 ReentrantLock,如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态,因为本质上它执行了 LockSupport.park()
方法,所以会进入 Waiting 状态。同样,Object.wait()
和 Thread.join()
也会让线程进入 Waiting 状态。
Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join
的线程执行完毕,或者是 notify()/notifyAll()
3.3.3 Timed_Waiting(限期等待)
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
以下情况会让线程进入 Timed_Waiting 状态。
- 设置了时间参数的
Thread.sleep(long millis)
方法; - 设置了时间参数的
Object.wait(long timeout)
方法; - 设置了时间参数的
Thread.join(long millis)
方法; - 设置了时间参数的
LockSupport.parkNanos(long nanos)
方法和LockSupport.parkUntil(long deadline)
方法。
3.3.4 阻塞状态流转到下一个状态说明
想要从 Blocked 状态进入 Runnable 状态,要求线程获取 monitor 锁即可。
而从 Waiting 状态流转到其他状态则比较特殊,因为首先 Waiting 是不限时的,也就是说无论过了多长时间它都不会主动恢复。只有当执行了 LockSupport.unpark()
,或者 join
的线程运行结束,或者被中断时才可以进入 Runnable 状态。如果其他线程调用 notify()
或 notifyAll()
来唤醒它,它会直接进入 Blocked 状态,这是因为唤醒 Waiting 线程的线程,如果调用 notify()
或 notifyAll()
,要求必须首先持有该 monitor 锁,所以如果处于 Waiting 状态的线程被唤醒时拿不到该锁,就会进入 Blocked 状态,直到执行了 notify()/notifyAll()
的唤醒它的线程执行完毕并释放 monitor 锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从 Blocked 状态回到 Runnable 状态。
同样在 Timed_Waiting 状态 中执行 notify()
和 notifyAll()
也是一样的道理,它们会先进入 Blocked 状态,然后抢夺锁成功后,再回到 Runnable 状态。当然对于 Timed_Waiting 而言,如果它的超时时间到了且能直接获取到锁/join的线程运行结束/被中断/调用了LockSupport.unpark()
,会直接恢复到 Runnable 状态,而无需经历 Blocked 状态。
3.4 终止状态(TERMINATED)
要想进入这个状态有两种可能:
-
run()
方法执行完毕,线程正常退出。 -
出现一个没有捕获的异常,终止了
run()
方法,最终导致意外终止。
注意:
- 当线程的
run()
方法完成时,或者主线程的main()
方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。- 在一个终止的线程上调用
start()
方法,会抛出java.lang.IllegalThreadStateException
异常。
3.5 线程转换注意事项
- 线程的状态是需要按照箭头方向来走的,比如线程从 New 状态是不可以直接进入 Blocked 状态的,它需要先经历 Runnable 状态。
- 线程生命周期不可逆:一旦进入 Runnable 状态就不能回到 New 状态;一旦被终止就不可能再有任何状态的变化。所以一个线程只能有一次 New 和 Terminated 状态,只有处于中间状态才可以相互转换。