Java 线程状态
线程状态
一、操作系统的线程状态
操作系统层面,线程状态经典模型可以概括为 “三态为主,五态展开”:
核心三态
- 就绪态(Ready):线程已具备运行条件,正在等待 CPU 调度,经调度程序选中后进入 Running 状态。
- 运行态(Running):线程正在实际占用 CPU 执行。
- 等待态(Waiting/Blocked):线程正在等待某个事件(如锁、信号量)或资源(如 I/O)完成,即使给它 CPU 也无法继续执行。
补充两态:线程被创建、分配资源后加入就绪队列,即进入 新建态(New);线程执行完毕后进入 终止态(Terminated)。五态合起来就是:New → Ready → Running → Blocked → Terminated。
经典转换关系图
创建
│
▼
┌───────┐
│ New │ (新建态)
└───┬───┘
│ 分配资源、加入就绪队列
▼
┌─────────┐ 调度器选中 ┌─────────┐
│ Ready │─────────────►│ Running │
│ (就绪态) │◄─────────────│ (运行态) │
└────┬────┘ 时间片用完 └────┬────┘
▲ │
│ 等待的事件/IO完成 │ 等待I/O、锁、信号量等
│ ▼
│ ┌──────────┐
└──────────────────│ Blocked │
│ (阻塞态/等待态) │
└──────────┘
Running ──执行完毕/被终止──► ┌────────────┐
│ Terminated │ (终止态)
└────────────┘
关键规则:Blocked 不能直接跳到 Running,必须先回到 Ready,重新参与调度后才可能拿到 CPU。
二、Java 的线程状态
核心结论
Java 线程有 6 种状态(
Thread.State枚举),是 JVM 层面的状态划分,和操作系统层面的线程状态不是一一对应的。最典型的区别是:Java 没有单独的"运行中(Running)"状态,就绪和运行中统一归为RUNNABLE。
// Thread.State 源码
public enum State {
NEW, // 新建状态
RUNNABLE, // 可运行状态
BLOCKED, // 阻塞状态
WAITING, // 等待状态
TIMED_WAITING, // 超时等待状态
TERMINATED // 终止状态
}
六种状态详解
| 状态 | 含义 | 进入方式 | 典型触发 API |
|---|---|---|---|
| NEW | 线程对象已创建,尚未启动 | new Thread() 之后、start() 之前 | — |
| RUNNABLE | 可运行(含"就绪"和"运行中") | 调用 start() 后 | — |
| BLOCKED | 阻塞,等待拿 synchronized 锁 | 争抢监视器锁失败 | 进入 synchronized 代码块/方法 |
| WAITING | 无限期等待,需别的线程唤醒 | 调用无参等待方法 | Object.wait()、Thread.join()、LockSupport.park() |
| TIMED_WAITING | 限期等待,超时自动返回 | 调用带超时参数的等待方法 | Thread.sleep(ms)、Object.wait(ms)、Thread.join(ms)、parkNanos() |
| TERMINATED | 线程执行结束 | run() 正常返回或抛出未捕获异常 | — |
状态转换图(文字版)
NEW ──start()──► RUNNABLE
RUNNABLE ──synchronized 抢锁失败──► BLOCKED
BLOCKED ──获得锁──► RUNNABLE
RUNNABLE ──wait()/join()──► WAITING
WAITING ──notify/notifyAll/目标线程结束/中断──► BLOCKED ──获得锁──► RUNNABLE
RUNNABLE ──park()──► WAITING
WAITING ──unpark/中断──► RUNNABLE
RUNNABLE ──sleep(t)/wait(t)/join(t)/parkNanos(t)──► TIMED_WAITING
TIMED_WAITING ──sleep 超时/中断──► RUNNABLE
TIMED_WAITING ──wait(t)/join(t) 超时/notify/中断──► BLOCKED ──获得锁──► RUNNABLE
TIMED_WAITING ──parkNanos 超时/unpark/中断──► RUNNABLE
RUNNABLE ──run() 结束/异常退出──► TERMINATED
start()
NEW ─────────────────────────► RUNNABLE
│
┌────────────────────┼─────────────────────┐
│ │ │
抢 synchronized 锁失败 wait() / join() park()
│ │ │
▼ ▼ ▼
BLOCKED WAITING WAITING
│ │ │
获得锁 notify / join结束 unpark
│ │ │
└──────────────► RUNNABLE ◄───────────────┘
RUNNABLE
│
├── sleep(t) ──────────────► TIMED_WAITING
│ │
│ 超时/中断
│ │
│ ▼
│ RUNNABLE
│
├── wait(t) ───────────────► TIMED_WAITING
│ │
│ 超时/notify/中断
│ ▼
│ BLOCKED
│ │
│ 获得锁
│ ▼
│ RUNNABLE
│
├── join(t) ───────────────► TIMED_WAITING
│ │
│ 超时/目标线程结束
│ ▼
│ RUNNABLE
│
└── parkNanos(t) ─────────► TIMED_WAITING
│
超时/unpark/中断
▼
RUNNABLE
RUNNABLE
│
└── run()结束/异常
▼
TERMINATED
可以。面试时建议不要只背“状态变化”,而是从 作用、是否释放锁、如何唤醒、是否抛异常 四个维度记忆。
Java 线程常见 API 面试速记表
| API | 作用 | 状态变化 | 是否释放锁 | 如何恢复 | 是否抛 InterruptedException |
|---|---|---|---|---|---|
sleep(t) | 让当前线程休眠一段时间 | RUNNABLE → TIMED_WAITING → RUNNABLE | 不释放锁 | 时间到 | 是 |
wait() | 当前线程等待,直到其他线程通知 | RUNNABLE → WAITING → BLOCKED → RUNNABLE | 释放当前对象锁 | notify/notifyAll 或中断 | 是 |
wait(t) | 最多等待指定时间 | RUNNABLE → TIMED_WAITING → BLOCKED → RUNNABLE | 释放当前对象锁 | 超时 / notify / 中断 | 是 |
notify() | 唤醒一个正在该对象上 wait() 的线程 | WAITING → BLOCKED | 不立即释放锁 | 被唤醒线程重新获得锁后继续 | 否 |
notifyAll() | 唤醒所有正在该对象上 wait() 的线程 | WAITING → BLOCKED | 不立即释放锁 | 竞争锁成功后继续 | 否 |
join() | 当前线程等待另一个线程执行结束 | RUNNABLE → WAITING → RUNNABLE | 通常不涉及释放当前业务锁 | 目标线程结束 | 是 |
join(t) | 最多等待另一个线程指定时间 | RUNNABLE → TIMED_WAITING → RUNNABLE | 通常不涉及释放当前业务锁 | 目标线程结束 / 超时 | 是 |
park() | 挂起当前线程 | RUNNABLE → WAITING | 不释放 Monitor 锁 | unpark() / 中断 | 否 |
parkNanos(t) | 挂起当前线程指定时间 | RUNNABLE → TIMED_WAITING | 不释放 Monitor 锁 | 超时 / unpark() / 中断 | 否 |
unpark(thread) | 允许指定线程从 park() 中恢复 | WAITING → RUNNABLE | 不涉及 | unpark() | 否 |
重点区别,面试直接背
| 对比 | sleep() | wait() | park() |
|---|---|---|---|
| 核心作用 | 休眠 | 等待通知 | 挂起线程 |
| 所属 | Thread | Object | LockSupport |
| 是否释放锁 | ❌ | ✅ | ❌ |
| 是否需要持有锁 | ❌ | 必须持有对象 Monitor 锁 | ❌ |
| 谁唤醒 | 时间到 | notify/notifyAll | unpark() |
| 中断 | 抛 InterruptedException | 抛 InterruptedException | 返回,不抛异常 |
join() 一句话理解
threadA.join();
就是:
当前线程暂停执行,等待 threadA 执行结束。
例如:
主线程
│
├── threadA.start()
│
├── threadA.join()
│ ↓
│ 等待 threadA
│
└── threadA结束后继续执行
wait/notify 最容易被问的细节
synchronized (obj) {
obj.wait();
}
wait():
释放锁 + 进入等待
synchronized (obj) {
obj.notify();
}
notify():
唤醒一个等待线程,但不会立即释放锁。
所以实际过程是:
线程 A:
持有 obj 锁
↓
wait()
↓
释放 obj 锁
↓
WAITING
线程 B:
获得 obj 锁
↓
notify()
↓
A 被唤醒
↓
A 进入 BLOCKED
↓
等待重新获得 obj 锁
↓
RUNNABLE
API 的作用
Thread.sleep():让当前线程休眠,不释放已持有的锁。
object.wait():让当前线程等待,释放当前对象的 Monitor 锁。
object.notify():唤醒一个等待线程,但不会立即释放当前持有的锁。
thread.join():让当前线程等待 thread 执行结束。
LockSupport.park():挂起当前线程,可以通过 unpark(thread) 恢复。
记忆技巧:所有"离开 RUNNABLE"的箭头都是主动调用某个方法造成的;所有"回到 RUNNABLE"的箭头都是被动被唤醒/超时/抢到锁造成的。
三、OS 状态 ↔ Java 状态 对照表 ⭐️
| OS 状态 | 对应的 Java Thread.State |
|---|---|
| New | NEW |
| Ready(就绪) | RUNNABLE(就绪部分) |
| Running(运行) | RUNNABLE(运行部分) |
| Blocked(等锁/等IO/等事件,统称) | BLOCKED(等 synchronized 锁)或 WAITING / TIMED_WAITING(等 wait/join/park/IO) |
| Terminated | TERMINATED |
为什么会有这种差异:
- Java 把 OS 的 Ready 和 Running 合并成一个
RUNNABLE,因为线程调度(CPU 时间片分配)由操作系统决定,JVM 不参与、也无法感知这个粒度。 - Java 把 OS 笼统的 Blocked 拆分成
BLOCKED/WAITING/TIMED_WAITING三种,是因为 JVM 想让开发者清楚"具体在等什么"——等锁、等条件、还是等超时。
四、高频考点
1. RUNNABLE 为什么不区分就绪和运行中?
线程调度(CPU 时间片分配)由操作系统决定,Java 虚拟机感知不到、也不需要感知这个粒度,所以统一算 RUNNABLE。
2. sleep() 和 wait() 的核心区别 ⭐️
| 对比项 | Thread.sleep() | Object.wait() |
|---|---|---|
| 所属类 | Thread(静态方法) | Object(实例方法) |
| 是否释放锁 | 不释放 | 释放 |
| 是否需要 synchronized | 不需要 | 必须在 synchronized 内调用,否则抛 IllegalMonitorStateException |
| 唤醒方式 | 超时自动醒 | notify()/notifyAll() 或超时(如果用带参版本) |
| 对应状态 | TIMED_WAITING | WAITING(无参)/ TIMED_WAITING(带参) |
synchronized (lock) {
Thread.sleep(1000); // 持有锁睡眠,其他线程仍然 BLOCKED,拿不到锁
lock.wait(); // 释放锁,其他线程可以进来执行
}
3. BLOCKED 和 WAITING 的区别 ⭐️(必考)
- BLOCKED:专指等待 synchronized 内置锁。
- WAITING / TIMED_WAITING:使用
Lock(如ReentrantLock)、LockSupport.park()、wait()、join()等方式等待时的状态,不算 BLOCKED。
一句话:
synchronized抢锁失败 →BLOCKED;ReentrantLock.lock()抢锁失败 →WAITING(内部基于 AQS +LockSupport.park()实现)。
4. 中断(interrupt)对各状态的影响
- 线程处于 WAITING / TIMED_WAITING 时被
interrupt():会抛出InterruptedException,线程被唤醒并回到RUNNABLE(需要在 catch 里处理)。 - 线程处于 RUNNABLE 时被
interrupt():不会自动停止,只是把中断标志位置为true,需要代码主动检查Thread.isInterrupted()来响应。 - BLOCKED 状态下被中断:不会抛异常,也不会立刻退出阻塞(
BLOCKED状态对中断不敏感,直到抢到锁才会检测到中断标志)。
5. 没有"死锁"对应的状态吗?
没有专门状态。两个线程互相等待对方持有的锁,双方都会停留在 BLOCKED(或 WAITING,取决于用的锁类型),通过 jstack 可以看到线程互相 BLOCKED 并被标注 “deadlock”。
6. TERMINATED 的线程能重新 start() 吗?
不能。线程只能启动一次,重复调用 start() 会抛 IllegalThreadStateException。
7. OS 的 Blocked 能不能直接变成 Running?
不能,必须先回到 Ready(就绪队列),重新参与调度后才可能拿到 CPU 进入 Running。
五、验证代码
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(500); // -> TIMED_WAITING
lock.wait(); // -> WAITING
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
System.out.println(t.getState()); // NEW
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TIMED_WAITING
Thread.sleep(600);
System.out.println(t.getState()); // WAITING
synchronized (lock) {
lock.notify();
}
t.join();
System.out.println(t.getState()); // TERMINATED
}
}
六、一句话总结
“操作系统层面线程有五态:New、Ready、Running、Blocked、Terminated,核心是就绪、运行、等待三态循环。Java 在此基础上做了 JVM 视角的重新划分,共 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。区别主要在于两点:一是 Java 把 OS 的 Ready 和 Running 合并成了 RUNNABLE,因为 JVM 不感知 CPU 调度;二是 Java 把 OS 笼统的 Blocked 拆分成了 BLOCKED(等 synchronized 锁)和 WAITING/TIMED_WAITING(等条件、超时、IO 等),方便开发者定位问题。”
七、追问
- 线程池中线程执行完一个任务后是什么状态?(仍是 RUNNABLE,在
getTask()里等待新任务,不会变成 TERMINATED) synchronized和ReentrantLock在状态表现上的区别?(见第四节第 3 点)- 如何用
jstack排查线程状态问题?(关注 BLOCKED 线程和持锁关系,排查死锁) wait()、notify()为什么要写在synchronized里?(保证等待和检查条件的原子性,避免"假唤醒"和竞态条件)- OS 的五态和七态模型区别?(七态多了 Ready Suspend / Blocked Suspend,涉及进程挂起/swap,操作系统内存管理场景才会用到,纯线程调度一般用五态即可)
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)