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

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

线程状态迁移的触发条件

Java 线程在 Thread.getState() 中返回 Thread.State 枚举,共 6 个值。要观测到全部状态,关键是构造能触发每种迁移的代码路径:

  • NEW:new Thread(...) 之后、start() 之前。
  • RUNNABLE:调用 start() 后,线程已就绪或正在运行(JVM 不区分就绪与运行)。
  • BLOCKED:等待进入 synchronized 临界区,锁被其他线程持有。
  • WAITING:调用 Object.wait()、Thread.join()、LockSupport.park() 等无超时等待。
  • TIMED_WAITING:调用 Thread.sleep(ms)、wait(ms)、join(ms) 等带超时等待。
  • TERMINATED:run() 正常返回或抛出未捕获异常后。

观测程序实现

下面的程序用一个观测线程每 50ms 打印目标线程状态,目标线程依次经历 6 种状态。为了让 BLOCKED 稳定出现,先让一个“持锁线程”占用监视器锁 800ms,目标线程随后尝试进入同一把锁的同步块。

public class ThreadStateDemo {

    static final Object LOCK = new Object();

    public static void main(String[] args) throws Exception {
        Thread target = new Thread(() -> {
            try {
                // 1. TIMED_WAITING:sleep 500ms
                Thread.sleep(500);

                // 2. BLOCKED:等待 LOCK 监视器
                synchronized (LOCK) {
                    // 3. WAITING:无超时 wait,需被 notify 唤醒
                    LOCK.wait();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "target-thread");

        // 观测线程:每 50ms 打印一次状态
        Thread observer = new Thread(() -> {
            Thread.State last = null;
            long deadline = System.currentTimeMillis() + 4000;
            while (System.currentTimeMillis() < deadline) {
                Thread.State now = target.getState();
                if (now != last) {
                    System.out.printf("[%6d ms] %s%n",
                            System.currentTimeMillis() % 100000, now);
                    last = now;
                }
                try { Thread.sleep(50); } catch (InterruptedException e) { return; }
            }
        }, "observer");

        System.out.println("start 前: " + target.getState()); // NEW
        observer.start();
        target.start();                                        // -> RUNNABLE

        // 持锁线程:先占住 LOCK 800ms,制造 BLOCKED
        Thread holder = new Thread(() -> {
            synchronized (LOCK) {
                try { Thread.sleep(800); } catch (InterruptedException ignored) {}
            }
        }, "lock-holder");
        holder.start();

        // 等目标线程进入 WAITING 后唤醒它
        Thread.sleep(2000);
        synchronized (LOCK) {
            LOCK.notifyAll();   // 唤醒 target,使其从 WAITING 返回
        }

        target.join();          // 主线程等待 target 结束
        observer.join();
        System.out.println("结束后: " + target.getState()); // TERMINATED
    }
}

状态迁移路径与观测点

下图给出本程序实际触发的状态迁移链,以及每个状态对应的触发代码和观测手段。注意 getState() 是瞬时快照,观测线程的采样间隔决定了能否捕获短状态。

线程状态迁移链与触发条件 NEW new Thread() RUNNABLE start() 后 TIMED_WAITING sleep(500) BLOCKED 等 LOCK 监视器 WAITING LOCK.wait() TERMINATED run() 返回 start() sleep(ms) sleep 结束 获得锁后 wait() notifyAll() 唤醒 → run() 结束 观测线程采样逻辑 每 50ms 调用 target.getState(),与上次状态不同才打印,避免刷屏。 采样间隔 50ms 远小于各状态持续时间(sleep 500ms、持锁 800ms),可稳定捕获。 BLOCKED 与 WAITING 的区别:前者在等锁,后者在等通知/唤醒信号。 TIMED_WAITING 与 WAITING 的区别:前者有超时参数,到点自动返回。 注:RUNNABLE 包含“就绪”与“运行中”,JVM 不单独暴露就绪态。

运行结果与判读

示例输出(时间戳为相对毫秒,实际数值随调度略有浮动):

start 前: NEW
[  102 ms] RUNNABLE
[  152 ms] TIMED_WAITING
[  652 ms] BLOCKED
[ 1452 ms] WAITING
[ 2052 ms] TERMINATED
结束后: TERMINATED

判读要点:

  • NEW 只在 start() 前出现,一旦 start() 返回就不可再回到 NEW。
  • BLOCKED 出现在 sleep 结束之后、进入 synchronized 之前,因为此时持锁线程仍占用 LOCK。
  • WAITING 出现在获得锁并调用 LOCK.wait() 之后,直到主线程 notifyAll() 才被唤醒。
  • TERMINATED 是终态,getState() 之后永远返回 TERMINATED。
若观测不到 BLOCKED,通常是持锁时间太短或目标线程先拿到了锁。可把持锁线程的 sleep 调大,或在目标线程进入同步块前加一个短暂 sleep 来稳定复现。
不要在 synchronized 块内调用 Thread.sleep() 来制造 BLOCKED,那会让当前线程进入 TIMED_WAITING 而非 BLOCKED。BLOCKED 只发生在“尝试获取监视器锁但未获得”的瞬间。