线程有几种状态,状态之间是怎样流转的?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

  1. 基础掌握度:面试官不仅仅是想知道有几种状态,更是想考察你是否能准确说出 Java 层面(Thread.State)的 6 种状态,而不是和操作系统的 5 种状态混为一谈。

  2. 状态流转理解:能否完整描述每种状态之间的转换条件,特别是 BLOCKEDWAITINGTIMED_WAITING 这三种 "停顿" 状态的区别和触发场景,这是面试的重点。

  3. 实践关联:能否结合 synchronizedLocksleep()wait() 等实际 API 来解释状态流转,而不是干巴巴背概念。

核心答案

Java 线程在 Thread.State 枚举中定义了 6 种状态

状态含义是否占用 CPU
NEW新建,还没调用 start()
RUNNABLE可运行(包括就绪和运行中)可能
BLOCKED阻塞,等待获取锁
WAITING等待,无限期等待被唤醒
TIMED_WAITING超时等待,有期限的等待
TERMINATED终止,线程执行完毕

一句话总结:6 种状态,核心流转路径是 NEW → RUNNABLE →(BLOCKED/WAITING/TIMED_WAITING)→ RUNNABLE → TERMINATED

深度解析

一、完整状态流转图

图片来源:https://mp.weixin.qq.com/s/0UTyrJpRKaKhkhHcQtXAiA

上图展示了 Java 线程 6 种状态之间的完整流转关系。下面逐条解释每条转换路径:

  • NEW → RUNNABLE:调用 start() 方法,线程从新建状态进入可运行状态,交给操作系统调度

  • RUNNABLE → BLOCKED:线程尝试进入 synchronized 代码块/方法,但锁被其他线程持有,进入阻塞状态

  • RUNNABLE → WAITING:线程调用 Object.wait()Thread.join()LockSupport.park() 等方法,主动进入无限期等待

  • RUNNABLE → TIMED_WAITING:线程调用 Thread.sleep(n)Object.wait(n)Thread.join(n) 等带超时的方法

  • BLOCKED → RUNNABLE:锁释放后,该线程竞争成功获取到锁

  • WAITING → RUNNABLE:被 notify()/notifyAll() 唤醒,或 LockSupport.unpark() 唤醒,或等待的线程执行完毕(join() 场景)

  • TIMED_WAITING → RUNNABLE:超时时间到自动唤醒,或在超时前被显式唤醒

  • WAITING/TIMED_WAITING → BLOCKED:被 notify() 唤醒后需要重新获取锁,如果锁被其他线程持有则进入 BLOCKED

  • RUNNABLE → TERMINATEDrun() 方法执行完毕,或抛出未捕获的异常

二、6 种状态逐一详解

1. NEW(新建)

线程对象已创建,但还没调用 start() 方法。此时线程还没分配系统资源。

Thread t = new Thread(() -> {}); // 此时状态:NEW
// t.start();  // 调用 start() 后才变为 RUNNABLE

2. RUNNABLE(可运行)

注意,Java 层面的 RUNNABLE 包含了操作系统层面的 就绪(Ready)运行中(Running) 两个状态。Java 不区分这两个,统一算 RUNNABLE

  • 就绪:线程已经准备好,等 CPU 分配时间片
  • 运行中:CPU 正在执行该线程
Thread t = new Thread(() -> {
    // 这里面执行时,状态就是 RUNNABLE
    // 即使调用了 IO 操作,Java 层面状态仍然是 RUNNABLE(这是 Java 的设计选择)
});
t.start(); // NEW → RUNNABLE

3. BLOCKED(阻塞)

线程在等待获取一把 内置锁synchronized)。当锁被其他线程持有时,尝试进入 synchronized 代码块/方法的线程就会变成 BLOCKED

synchronized (lock) {       // 如果 lock 被其他线程持有
    // 线程状态:BLOCKED     // 等待获取锁
    // 获取到锁后 → RUNNABLE
}

注意BLOCKED 只针对 synchronized,使用 ReentrantLock.lock() 时线程进入的是 WAITING(因为底层调用了 LockSupport.park()),不是 BLOCKED

4. WAITING(无限期等待)

线程进入等待状态,需要其他线程显式唤醒。以下操作会让线程进入 WAITING

方法唤醒条件
Object.wait()需要 notify()/notifyAll()
Thread.join()等待目标线程执行完毕
LockSupport.park()需要 LockSupport.unpark()
// 示例:wait/notify
synchronized (lock) {
    lock.wait();   // RUNNABLE → WAITING
    // 等其他线程调用 lock.notify()
    // 被唤醒后 → BLOCKED(重新抢锁)
    // 抢到锁后 → RUNNABLE
}

5. TIMED_WAITING(超时等待)

WAITING 类似,区别是 有超时时间,时间到了自动唤醒。以下操作会让线程进入 TIMED_WAITING

方法说明
Thread.sleep(long)休眠指定时间
Object.wait(long)超时等待
Thread.join(long)超时等待目标线程
LockSupport.parkNanos()超时阻塞
LockSupport.parkUntil()阻塞到指定时间点
Thread.sleep(1000);  // RUNNABLE → TIMED_WAITING
                      // 1 秒后自动 → RUNNABLE

6. TERMINATED(终止)

线程执行完毕或因异常退出,不可再启动。

Thread t = new Thread(() -> {});
t.start();
t.join();          // 等待线程执行完
// 此时状态:TERMINATED

三、三种 "停顿" 状态的区别(面试重点)

BLOCKEDWAITINGTIMED_WAITING 都表示线程 "没在跑",但本质不同:

对比维度BLOCKEDWAITINGTIMED_WAITING
触发原因等待 synchronizedwait()/join()/park()sleep()/wait(n)/parkNanos()
等待时长不确定(等锁释放)不确定(等别人唤醒)确定的超时时间
唤醒方式锁释放后自动竞争需要显式 notify()/unpark()超时自动唤醒,或被显式唤醒
是否释放锁还没获取到锁wait() 会释放锁wait(n) 会释放锁
jstack 中常见waiting for monitor entrywaiting on condition / Object.wait()sleeping / parking to wait

常见误区澄清

  • Thread.sleep() 不会释放锁,但 Object.wait() 会释放锁
  • synchronized 获取锁失败 → BLOCKEDReentrantLock.lock() 等待 → WAITING(因为底层用 LockSupport.park()
  • Java 的 IO 阻塞(如 Socket.read())在 Java 层面状态仍然是 RUNNABLE,不会变成 BLOCKEDWAITING,这是因为 Java 把操作系统层面的 IO 阻塞也归入了 RUNNABLE

四、常见 API 对应的状态流转速查

API 调用状态变化恢复条件
t.start()NEW → RUNNABLE-
Thread.sleep(n)RUNNABLE → TIMED_WAITING超时自动恢复
Object.wait()RUNNABLE → WAITINGnotify()/notifyAll()
Object.wait(n)RUNNABLE → TIMED_WAITING超时 或 notify()
Thread.join()RUNNABLE → WAITING目标线程执行完毕
Thread.join(n)RUNNABLE → TIMED_WAITING超时 或目标线程执行完
LockSupport.park()RUNNABLE → WAITINGunpark() 或中断
LockSupport.parkNanos()RUNNABLE → TIMED_WAITING超时 或 unpark()
进入 synchronized(锁被占)RUNNABLE → BLOCKED锁释放后竞争成功
notify() 唤醒后WAITING → BLOCKED获取到锁后 → RUNNABLE
run() 执行完RUNNABLE → TERMINATED不可逆

面试高频追问

  1. Java 线程状态和操作系统线程状态有什么区别?
    • 操作系统层面线程有 5 种状态(创建、就绪、运行、阻塞、终止),Java 的 RUNNABLE 对应了 OS 的 "就绪 + 运行",Java 的 BLOCKEDWAITINGTIMED_WAITING 都对应 OS 的 "阻塞"。另外 Java 把 BIO 的阻塞也归入 RUNNABLE,而 OS 层面是 "阻塞"。
  2. BLOCKEDWAITING 有什么区别?
    • BLOCKED 是等待获取 synchronized 锁(被动阻塞),WAITING 是主动调用 wait()/join()/park() 进入等待(主动等待)。BLOCKED 不需要别人唤醒,锁一释放就自动去抢;WAITING 必须等别人显式唤醒。
  3. sleep()wait() 的状态变化有什么不同?
    • sleep(n) 让线程进入 TIMED_WAITING,不释放锁;wait() 让线程进入 WAITING,会释放锁。sleep() 到时间自动恢复,wait() 需要别人 notify()

常见面试变体

  • "说说 Thread.sleep()Object.wait() 的区别?"
  • "BLOCKEDWAITING 有什么区别?"
  • "如何用 jstack 排查线程阻塞问题?"
  • "为什么 Java 没有区分就绪和运行两个状态?"

记忆口诀

6 种状态:新建、可运行、阻塞、等待、超时等待、终止

流转主线:NEW(出生)→ RUNNABLE(上班)→ 三种摸鱼姿势(BLOCKED 等锁、WAITING 等通知、TIMED_WAITING 等闹钟)→ RUNNABLE(继续干活)→ TERMINATED(下班)

三种等待区分:BLOCKED 被锁挡、WAITING 等人叫、TIMED_WAITING 等闹钟

总结

Java 线程有 6 种状态:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED。核心流转是创建后 start() 进入 RUNNABLE,运行过程中可能因锁竞争进入 BLOCKED,因 wait()/join()/park() 进入 WAITING,因 sleep()/wait(n) 进入 TIMED_WAITING,最终执行完毕进入 TERMINATED。重点区分三种 "停顿" 状态的触发条件和恢复方式。