线程状态
一、操作系统的线程状态
操作系统层面,线程状态经典模型可以概括为 "三态为主,五态展开":
核心三态
- 就绪态(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。
java
// 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()/park()──► WAITING
WAITING ──notify()/notifyAll()/目标线程结束/unpark()/中断──► RUNNABLE
RUNNABLE ──sleep(t)/wait(t)/join(t)──► TIMED_WAITING
TIMED_WAITING ──超时/被唤醒/被中断──► RUNNABLE
RUNNABLE ──run()执行完/抛异常──► TERMINATED
记忆技巧:所有"离开 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(带参) |
java
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。
五、验证代码
java
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,操作系统内存管理场景才会用到,纯线程调度一般用五态即可)