图 1:本文封面,仪表盘上的六枚节点就是线程一生的全部落脚点。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。这道题表面考背诵,实际考的是排障地图:线上线程转储里的每一行状态,都要能翻译成「这个线程此刻在等什么」。
想象面试现场:「Java 线程有哪几种状态?wait、sleep、join 分别让线程走到哪里?BLOCKED 和 WAITING 有什么区别?」
核心结论一句话
JDK 官方枚举了六种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。记忆框架就一条分界线:BLOCKED 在等锁,WAITING 与 TIMED_WAITING 在等别人唤醒或等时钟;排障时先按这条线分类,再看具体等待点。
逐个走一遍状态机。
NEW:new 了 Thread 但没调 start,线程还只是堆里的一个对象。RUNNABLE:start 之后进入,官方定义是「正在 JVM 中执行」——注意它把「正在 CPU 上跑」和「就绪等调度」合并成了一个状态,甚至可能正在等操作系统资源,所以看到 RUNNABLE 不等于它在干活。TERMINATED:run 方法执行完毕,不可逆,线程一生结束。BLOCKED:等着进 synchronized 块或方法,也就是等一把监视器锁;拿到锁就回 RUNNABLE。WAITING:无限期等另一个线程的特定动作——无超时的 Object.wait、无超时的 Thread.join、LockSupport.park 都进这里,等 notify 或目标线程结束才回来。TIMED_WAITING:带超时的等待——sleep、带超时的 wait 与 join、parkNanos、parkUntil,时间到自动醒来。
一段代码把六种状态全部打印出来。
下面这段程序在 JDK 17 上编译运行(java ThreadStates.java),让六个状态各露一次脸:
public class ThreadStates {
static final Object LOCK = new Object();
public static void main(String[] args) throws Exception {
// RUNNABLE:主线程此刻正在 JVM 里执行
System.out.println("主线程自己: " + Thread.currentThread().getState());
// NEW / WAITING / TERMINATED:t 调用无超时 wait 后挂起
Thread t = new Thread(() -> {
synchronized (LOCK) {
try { LOCK.wait(); } catch (InterruptedException e) { }
}
});
System.out.println("t 新建未 start: " + t.getState());
t.start();
Thread.sleep(100); // 等 t 进入 wait
System.out.println("t 调用 wait() 后: " + t.getState());
// BLOCKED:u 想进 synchronized,但 LOCK 被主线程占着
Thread u = new Thread(() -> { synchronized (LOCK) { } });
synchronized (LOCK) {
u.start();
Thread.sleep(100);
System.out.println("u 抢不到锁时: " + u.getState());
}
u.join();
// TIMED_WAITING:s 正在 sleep
Thread s = new Thread(() -> {
try { Thread.sleep(3000); } catch (InterruptedException e) { }
});
s.start();
Thread.sleep(100);
System.out.println("s 正在 sleep 时: " + s.getState());
// TERMINATED:把 t 唤醒并等它跑完
synchronized (LOCK) { LOCK.notifyAll(); }
t.join();
System.out.println("t 跑完之后: " + t.getState());
s.interrupt();
}
}
实测输出:
主线程自己: RUNNABLE
t 新建未 start: NEW
t 调用 wait() 后: WAITING
u 抢不到锁时: BLOCKED
s 正在 sleep 时: TIMED_WAITING
t 跑完之后: TERMINATED
图 2:六状态与全部转换路径,一张图背下来。
可能的追问与易错点。
「线上怎么拿到这些状态?」两条路:代码里用 Thread.getState() 主动读,排障时对整个 JVM 做一次线程转储(jstack 或 kill -3),每个线程后面跟着的就是它此刻的状态单词。看到大片 BLOCKED 指向同一把锁,是锁竞争;看到某个线程长期 WAITING 无人唤醒,就要检查是不是 notify 丢了或 park 之后没人 unpark。
「wait 和 sleep 的区别?」这是最经典的追问,答两个点:一是锁——wait 会释放持有的监视器锁让别人进来,sleep 不释放任何锁(官方文档原话:线程不会失去任何监视器的所有权),抱着锁睡觉是死锁的温床;二是归属——wait 是 Object 的方法、必须在 synchronized 里调用,sleep 是 Thread 的静态方法、哪里都能调。「wait 醒了之后直接进入 RUNNABLE 吗?」不一定:被 notify 唤醒后要先重新抢锁,抢不到会先卡在 BLOCKED。易错点有三:一是把 RUNNABLE 理解成「正在运行」,线程转储里成百上千个 RUNNABLE 其实大部分在等调度或等 IO;二是分不清 BLOCKED 与 WAITING,前者诊断方向是锁竞争,后者是唤醒逻辑或超时设置;三是以为可以重启一个 TERMINATED 线程,start 只能调一次。
回到面试现场,建议的答题节奏:先一口气报出六个状态,再画状态机讲三条等待路径,然后用「等锁还是等唤醒」区分 BLOCKED 与 WAITING,最后落一个排障场景收尾——推理请求排队、异步任务卡死、线程池耗尽,线上拿到的第一线索就是转储里每行 java.lang.Thread.State 后面的那个单词,读懂它,问题就定位了一半。状态机是并发排障的通用语言,和面试官对齐一次,工作里天天用得上。
你在线上线程转储里见过最诡异的状态组合是什么?欢迎聊聊。