参考文献: 深入理解Java并发框架AQS系列(一):线程
1. 概述
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过 6 种状态:
- NEW-初始
- RUNNABLE-可运行
- BLOCKED-阻塞
- WAITING-等待
- TIMED_WAITING-超时等待
- TERMINATED-结束
为了论述的更为彻底,我们站在操作系统的角度,将 RUNNABLE-可运行 状态拆分为 runnable-就绪状态 及 running-运行状态,故一共7种状态。
我尝试想用一张图把状态流转描述清楚,发现非常困难,由于wait/notify
使用的特殊性,会将整个流程图搅得很乱,所以此处我们把状态流转拆分为(非wait方法)及(wait方法)。
1. 新建状态(new)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
2. 就绪状态(runnable)
当线程对象调用了 start() 方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
就绪状态表示当前线程已经启动,只要操作系统调度了cpu时间片,即可运行,其本质上还是处于等待;例如3个正常启动且无阻塞的线程,运行在一个2核的计算机上,那么在某一个时刻,一定至少有1个线程处于就绪状态,等待着cpu资源
3. 运行状态(running)
唯一一个正在运行中的状态,且当前线程没有阻塞、休眠、挂起等;处于此状态的线程,通过主动调用Thread.yield()
方法,可变为就绪状态
4. 阻塞状态(blocked)
线程被动地处于synchronized
的阻塞队列中,没有超时概念、不响应中断
5. 等待状态(waiting)
顾名思义,线程处于主动等待中,且响应中断;当线程主动调用了以下3个方法时,即处于等待状态,等待其他线程的唤起
- Thread.join()
- LockSupport.park()
- Object.wait()
与阻塞状态的区别:
- 阻塞状态:线程总是被动的处于阻塞状态,当一个线程执行 synchronized 代码块时,它不知道自己马上抢到锁并执行后续逻辑还是会被阻塞
- 等待状态:线程很清楚自己接下来要处于等待状态,而且这个命令是线程自己发起的,即便何时被唤醒它无法控制
6. 超时等待(timed_waiting)
此状态与waiting
状态定义基本一致,只是引入了超时概念;进入timed_waiting
的方法如下:
Thread.sleep(long)
Thread.join(long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(long)
Object.wait(long)
7. 线程死亡(dead)
线程会以下面三种方式结束,结束后就是死亡状态。
-
正常结束
run()或 call()方法执行完成,线程正常结束。
-
异常结束
线程抛出一个未捕获的 Exception 或 Error。
-
调用 stop
直接调用该线程的 stop() 方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
8. 状态流转
初始 -> 就绪
线程调用Thread.start()
方法即可进入就绪状态
就绪 -> 运行
操作系统调度,JVM层面无法干预
运行 -> 就绪
分主动、被动2种方式
- 1、当前线程的cpu时间片用完,被动进入就绪状态
- 2、主动调用
Thread.yield()
运行 -> 阻塞
2种场景可将一个运行状态的线程变为阻塞状态,且都与synchronized
相关
- 场景1:线程因争抢
synchronized
锁失败,从而进入等待队列时,线程状态置为blocked
- 场景2:处于
Object.wait()
的线程在被唤醒后,不会立即去执行后续代码,而且是会重新争抢synchronized
锁,争抢失败的即会进入同步队列排序,此时的线程状态同样为blocked
运行 -> 等待
- 场景1:调用
Thread.join()
- 场景2:调用
LockSupport.park()
,即挂起线程,且只能挂起当前线程 - 场景 3:调用
Object.await()
运行 -> 超时等待
Thread.sleep(long)
Thread.join(long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(long)
等待/超时等待 -> 阻塞
当执行完Object.wait()/Object.wait(long)
后,不会马上进入就绪状态,线程间还要继续争抢同步队列的锁,争抢失败的便会进入阻塞状态;
运行 -> 终止
线程正常执行完毕,结束了run
方法后便进入终止状态,无法再被唤起,等待GC回收