Java线程的生命周期有哪些状态?从RUNNABLE状态分别进入BLOCKED、WAITING、TIMED_WAITING状态的典型场景是什么?如何通过JDK工具观察线程状态?
一、Java线程的生命周期:6种状态的“人生轨迹”
Java线程的一生就像“打工的职业生涯”,会经历6种状态,按流转顺序可分为:
NEW(新建):线程刚被创建(比如new Thread()),但还没调用start()方法,就像“刚拿到offer,还没入职”。RUNNABLE(可运行):调用start()后进入此状态,表示“正在运行或随时可运行”(具体是否在CPU上执行,由操作系统调度),就像“已入职,要么正在干活,要么在工位上等任务”。BLOCKED(阻塞):线程在等“锁”(比如synchronized锁),就像“想进会议室开会,但门锁着,得等别人出来”。WAITING(无限等待):线程在等某个“通知”,不收到通知就一直等,就像“老板说‘等我通知再干活’,没通知就一直等,不知道等多久”。TIMED_WAITING(限时等待):线程在等“有时间限制的通知”,超时后自动结束等待,就像“老板说‘等10分钟,没来通知就先干别的’”。TERMINATED(终止):线程执行完了(或异常结束),就像“工作结束,离职了”。
二、RUNNABLE状态进入其他状态的典型场景
RUNNABLE是线程最“活跃”的状态,切换到其他状态通常和“等待资源”或“主动暂停”有关:
1. 进入BLOCKED状态:等“锁”的时候
场景:线程试图进入synchronized同步块/方法,但锁已被其他线程占用。
比如线程A拿着锁在执行synchronized代码,线程B也想进这个代码块,就会从RUNNABLE变成BLOCKED,直到线程A释放锁。
2. 进入WAITING状态:等“无条件通知”的时候
场景1:调用Object.wait()方法(没带超时参数)。
线程拿到锁后,调用wait()会释放锁并进入WAITING,直到其他线程调用notify()或notifyAll()唤醒它。场景2:调用Thread.join()方法(没带超时参数)。
线程A调用线程B.join(),会进入WAITING,直到线程B执行完才恢复。场景3:调用LockSupport.park()方法。
线程被“暂停”,直到其他线程调用LockSupport.unpark(线程)唤醒它。
3. 进入TIMED_WAITING状态:等“有时间限制的事件”的时候
场景1:调用带超时的Object.wait(long timeout)。
线程等timeout毫秒,超时没被唤醒就自动恢复。场景2:调用带超时的Thread.sleep(long millis)。
线程主动“睡”millis毫秒,期间不占用CPU,时间到后自动回到RUNNABLE。场景3:调用带超时的Thread.join(long millis)。
线程A等线程B最多millis毫秒,超时后不管线程B是否执行完,都恢复运行。场景4:调用带超时的LockSupport.parkNanos()或parkUntil()。
线程暂停指定时间(纳秒)或到指定时间点,自动恢复。
三、如何用JDK工具观察线程状态
JDK自带的工具能直观看到线程的状态,常用的有:
jps + jstack:
先用jps命令查看Java进程的ID(比如1234);再用jstack 1234命令打印线程堆栈,其中每个线程的状态会明确标注(比如BLOCKED (on object monitor)、WAITING (parking)等),还能看到线程在等什么锁、在哪行代码阻塞。
jconsole:
图形化工具,打开后连接目标进程,在“线程”标签页能看到所有线程的状态(下拉列表可筛选特定状态的线程),点击线程还能查看详细的调用栈,适合快速定位“卡在哪里”。
VisualVM:
更强大的图形化工具,在“线程”面板中,线程状态用不同颜色标注(比如绿色是RUNNABLE,红色是BLOCKED),还能生成线程快照,对比不同时间点的状态变化,方便分析状态异常(比如大量线程长期处于BLOCKED可能是死锁)。
总结
Java线程的生命周期就像“从入职到离职”的过程,核心状态是RUNNABLE,而BLOCKED、WAITING、TIMED_WAITING都是“等待状态”(区别在于等什么、等多久)。通过jstack、jconsole等工具,能清晰看到线程的状态和阻塞原因,是排查线程问题的关键手段。