线程有几种状态,状态之间是怎样流转的?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 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/
面试考察点
-
基础掌握度:面试官不仅仅是想知道有几种状态,更是想考察你是否能准确说出 Java 层面(
Thread.State)的 6 种状态,而不是和操作系统的 5 种状态混为一谈。 -
状态流转理解:能否完整描述每种状态之间的转换条件,特别是
BLOCKED、WAITING、TIMED_WAITING这三种 "停顿" 状态的区别和触发场景,这是面试的重点。 -
实践关联:能否结合
synchronized、Lock、sleep()、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 → TERMINATED:
run()方法执行完毕,或抛出未捕获的异常
二、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
三、三种 "停顿" 状态的区别(面试重点)
BLOCKED、WAITING、TIMED_WAITING 都表示线程 "没在跑",但本质不同:
| 对比维度 | BLOCKED | WAITING | TIMED_WAITING |
|---|---|---|---|
| 触发原因 | 等待 synchronized 锁 | wait()/join()/park() | sleep()/wait(n)/parkNanos() |
| 等待时长 | 不确定(等锁释放) | 不确定(等别人唤醒) | 确定的超时时间 |
| 唤醒方式 | 锁释放后自动竞争 | 需要显式 notify()/unpark() | 超时自动唤醒,或被显式唤醒 |
| 是否释放锁 | 还没获取到锁 | wait() 会释放锁 | wait(n) 会释放锁 |
jstack 中常见 | waiting for monitor entry | waiting on condition / Object.wait() | sleeping / parking to wait |
常见误区澄清:
Thread.sleep()不会释放锁,但Object.wait()会释放锁synchronized获取锁失败 →BLOCKED;ReentrantLock.lock()等待 →WAITING(因为底层用LockSupport.park())- Java 的 IO 阻塞(如
Socket.read())在 Java 层面状态仍然是RUNNABLE,不会变成BLOCKED或WAITING,这是因为 Java 把操作系统层面的 IO 阻塞也归入了RUNNABLE
四、常见 API 对应的状态流转速查
| API 调用 | 状态变化 | 恢复条件 |
|---|---|---|
t.start() | NEW → RUNNABLE | - |
Thread.sleep(n) | RUNNABLE → TIMED_WAITING | 超时自动恢复 |
Object.wait() | RUNNABLE → WAITING | notify()/notifyAll() |
Object.wait(n) | RUNNABLE → TIMED_WAITING | 超时 或 notify() |
Thread.join() | RUNNABLE → WAITING | 目标线程执行完毕 |
Thread.join(n) | RUNNABLE → TIMED_WAITING | 超时 或目标线程执行完 |
LockSupport.park() | RUNNABLE → WAITING | unpark() 或中断 |
LockSupport.parkNanos() | RUNNABLE → TIMED_WAITING | 超时 或 unpark() |
进入 synchronized(锁被占) | RUNNABLE → BLOCKED | 锁释放后竞争成功 |
notify() 唤醒后 | WAITING → BLOCKED | 获取到锁后 → RUNNABLE |
run() 执行完 | RUNNABLE → TERMINATED | 不可逆 |
面试高频追问
- Java 线程状态和操作系统线程状态有什么区别?
- 操作系统层面线程有 5 种状态(创建、就绪、运行、阻塞、终止),Java 的
RUNNABLE对应了 OS 的 "就绪 + 运行",Java 的BLOCKED、WAITING、TIMED_WAITING都对应 OS 的 "阻塞"。另外 Java 把 BIO 的阻塞也归入RUNNABLE,而 OS 层面是 "阻塞"。
- 操作系统层面线程有 5 种状态(创建、就绪、运行、阻塞、终止),Java 的
BLOCKED和WAITING有什么区别?BLOCKED是等待获取synchronized锁(被动阻塞),WAITING是主动调用wait()/join()/park()进入等待(主动等待)。BLOCKED不需要别人唤醒,锁一释放就自动去抢;WAITING必须等别人显式唤醒。
sleep()和wait()的状态变化有什么不同?sleep(n)让线程进入TIMED_WAITING,不释放锁;wait()让线程进入WAITING,会释放锁。sleep()到时间自动恢复,wait()需要别人notify()。
常见面试变体
- "说说
Thread.sleep()和Object.wait()的区别?" - "
BLOCKED和WAITING有什么区别?" - "如何用
jstack排查线程阻塞问题?" - "为什么 Java 没有区分就绪和运行两个状态?"
记忆口诀
6 种状态:新建、可运行、阻塞、等待、超时等待、终止
流转主线:NEW(出生)→ RUNNABLE(上班)→ 三种摸鱼姿势(BLOCKED 等锁、WAITING 等通知、TIMED_WAITING 等闹钟)→ RUNNABLE(继续干活)→ TERMINATED(下班)
三种等待区分:BLOCKED 被锁挡、WAITING 等人叫、TIMED_WAITING 等闹钟
总结
Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。核心流转是创建后 start() 进入 RUNNABLE,运行过程中可能因锁竞争进入 BLOCKED,因 wait()/join()/park() 进入 WAITING,因 sleep()/wait(n) 进入 TIMED_WAITING,最终执行完毕进入 TERMINATED。重点区分三种 "停顿" 状态的触发条件和恢复方式。