深蓝黑背景品牌封面:左侧竖排大标题「线程的六种状态」,副标题「一张状态机图说清楚」;右侧原创装饰——一个圆形仪表盘外圈均匀分布六枚小节点,其中一枚青色高亮并以弧线指向下一枚,寓意线程在生命周期状态之间流转;左上标注「经典题解析 · Java 后端」,右上标注「Day 06 · 10-14」 图 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

状态机图:中央是 RUNNABLE 运行态大圆角矩形。左上是 NEW 新建态,start 之后进入 RUNNABLE。右上是 TERMINATED 终止态,run 方法执行完毕后进入且不可逆。右侧中部是 BLOCKED 阻塞态,等锁进入、拿到锁回到 RUNNABLE。左下是 WAITING 无限等待态,wait、join、park 进入,notify 或目标线程结束才回来。中下是 TIMED_WAITING 超时等待态,sleep、带超时 wait 与 join 进入,时间到自动醒来。底部提示:BLOCKED 与两种等待态的区别在于——阻塞在等锁,等待在等别人唤醒或等时钟,排障时先按这条线分类 图 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 后面的那个单词,读懂它,问题就定位了一半。状态机是并发排障的通用语言,和面试官对齐一次,工作里天天用得上。

你在线上线程转储里见过最诡异的状态组合是什么?欢迎聊聊。