2. 线程生命周期状态观测

线程生命周期状态观测 学习卡片

线程状态迁移的观测目标

Java 线程在 Thread.getState() 中暴露 6 个枚举值:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。本案例用一个可复现的程序,让同一条线程依次进入这 6 个状态,并在每个阶段用 getState() 打印,验证状态迁移的触发条件。

关键点:getState() 返回的是调用瞬间的瞬时状态,不是历史轨迹。因此必须让线程在目标状态上“停留”足够长,主线程才能稳定观测到它。

状态迁移的触发条件

NEW new Thread() 后 RUNNABLE start() 后 / 就绪或运行 TERMINATED run() 正常结束 BLOCKED 等待 synchronized 锁 WAITING wait() / join() 无超时 TIMED_WAITING sleep(n) / wait(n) start() run() 返回 竞争锁失败 获得锁 wait() / join() notify() / 目标结束 sleep(n) / wait(n) 超时 / notify() getState() 观测要点 • NEW:线程对象已创建,尚未调用 start(),此时 getState() 返回 NEW • RUNNABLE:调用 start() 后进入,包含“就绪”和“运行中”两种子状态,JVM 不区分 • BLOCKED:仅由 synchronized 锁竞争引起,与 wait() 无关 • WAITING / TIMED_WAITING:wait()、join()、sleep() 等阻塞方法触发,区别在于是否有超时参数 • TERMINATED:run() 执行完毕或异常退出,状态不可逆

可复现的观测程序

下面的程序用一条工作线程依次经历 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() 做精确的时序断言,它只适合做状态存在性验证。