2. 线程生命周期状态观测
线程状态迁移的观测目标
Java 线程在 Thread.getState() 中暴露 6 个枚举值:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。本案例用一个可复现的程序,让同一条线程依次进入这 6 个状态,并在每个阶段用 getState() 打印,验证状态迁移的触发条件。
关键点:
getState() 返回的是调用瞬间的瞬时状态,不是历史轨迹。因此必须让线程在目标状态上“停留”足够长,主线程才能稳定观测到它。
状态迁移的触发条件
可复现的观测程序
下面的程序用一条工作线程依次经历 6 个状态。为了让主线程能稳定捕获每个状态,工作线程在关键状态上使用 CountDownLatch 与主线程同步,而不是依赖 sleep 的时序猜测。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ThreadStateDemo {
// 用于让工作线程在目标状态上“停住”,等待主线程观测
static final CountDownLatch enteredBlocked = new CountDownLatch(1);
static final CountDownLatch enteredWaiting = new CountDownLatch(1);
static final CountDownLatch enteredTimedWaiting = new CountDownLatch(1);
// 锁对象:制造 BLOCKED 状态
static final Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread worker = new Thread(() -> {
// 1. 进入 BLOCKED:尝试获取被主线程持有的锁
synchronized (lock) {
// 拿到锁后立即通知主线程
enteredBlocked.countDown();
}
// 2. 进入 WAITING:调用 lock.wait() 无超时
synchronized (lock) {
try {
enteredWaiting.countDown();
lock.wait(); // 释放锁并进入 WAITING
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 3. 进入 TIMED_WAITING:调用 sleep(2000)
try {
enteredTimedWaiting.countDown();
Thread.sleep(2000); // 进入 TIMED_WAITING
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 4. 正常结束,进入 TERMINATED
}, "worker");
// ---- 观测 NEW ----
System.out.println("[1] 创建后: " + worker.getState()); // NEW
// ---- 观测 RUNNABLE ----
worker.start();
Thread.sleep(50); // 给调度器一点时间
System.out.println("[2] start() 后: " + worker.getState()); // RUNNABLE
// ---- 观测 BLOCKED ----
synchronized (lock) {
// 主线程持有锁,worker 在 synchronized(lock) 处阻塞
Thread.sleep(100);
System.out.println("[3] 竞争锁时: " + worker.getState()); // BLOCKED
}
enteredBlocked.await(1, TimeUnit.SECONDS);
// ---- 观测 WAITING ----
synchronized (lock) {
enteredWaiting.await(1, TimeUnit.SECONDS);
Thread.sleep(100);
System.out.println("[4] wait() 后: " + worker.getState()); // WAITING
lock.notify(); // 唤醒 worker
}
// ---- 观测 TIMED_WAITING ----
enteredTimedWaiting.await(1, TimeUnit.SECONDS);
Thread.sleep(100);
System.out.println("[5] sleep(2000) 时: " + worker.getState()); // TIMED_WAITING
// ---- 观测 TERMINATED ----
worker.join();
System.out.println("[6] 结束后: " + worker.getState()); // TERMINATED
}
}
运行结果与判读
在 JDK 8+ 上运行,预期输出如下(顺序固定,状态值固定):
[1] 创建后: NEW
[2] start() 后: RUNNABLE
[3] 竞争锁时: BLOCKED
[4] wait() 后: WAITING
[5] sleep(2000) 时: TIMED_WAITING
[6] 结束后: TERMINATED
如果第 [3] 步打印出 RUNNABLE 而不是 BLOCKED,通常是因为主线程尚未真正持有锁,worker 已经先拿到了锁。解决方法是确保主线程先进入
synchronized(lock) 块,再启动 worker,或增加同步信号。
状态迁移条件速查
| 目标状态 | 触发条件 | 退出条件 |
|---|---|---|
NEW |
new Thread() 之后,start() 之前 |
调用 start() |
RUNNABLE |
start() 被调用,或阻塞状态被解除 |
进入阻塞、等待或运行结束 |
BLOCKED |
竞争 synchronized 锁失败 |
获得锁 |
WAITING |
Object.wait()、Thread.join()(无超时) |
notify()/notifyAll() 或目标线程结束 |
TIMED_WAITING |
Thread.sleep(n)、wait(n)、join(n) |
超时到期或被提前唤醒 |
TERMINATED |
run() 正常返回或抛出未捕获异常 |
不可逆 |
getState() 是瞬时快照。在多核机器上,即使线程处于 RUNNABLE,也可能只是“就绪”而未被 CPU 调度。不要用 getState() 做精确的时序断言,它只适合做状态存在性验证。