📌 人工智能开发:AI Agents 开发实践
第17题:线程有几种状态,之间如何转换?
📚 回答:
- 核心考点: Java 线程状态是 JVM 与操作系统线程映射的核心概念。大厂面试不会只问"有哪 6 种状态",而是深入考察 JVM 线程状态与操作系统线程状态的映射关系(RUNNABLE 包含 Running 和 Ready)、BLOCKED、WAITING、TIMED_WAITING 的底层实现差异(Monitor 锁 vs LockSupport.park vs 条件变量)、以及 Thread.State 枚举的设计哲学与 jstack 实战排查。面试官真正想判断的是:你是否能从 JVM 源码层面理解线程状态的本质,以及能否在生产环境中通过线程状态定位性能问题。
1. Java 线程的 6 种状态——Thread.State 枚举
Java 在 java.lang.Thread.State 中定义了 6 种线程状态,这是 JVM 对操作系统线程状态的抽象和简化:
| NEW | Thread.State.NEW | 线程已创建,未启动 | new Thread() | 无(只能 → RUNNABLE) |
| RUNNABLE | Thread.State.RUNNABLE | 可运行(含运行中 + 就绪等待调度) | start() | 无(调度器分配 CPU) |
| BLOCKED | Thread.State.BLOCKED | 阻塞等待 Monitor 锁 | synchronized 竞争失败 | 锁被释放 |
| WAITING | Thread.State.WAITING | 无限期等待,需显式唤醒 | wait()/join()/LockSupport.park() | notify()/notifyAll()/unpark() |
| TIMED_WAITING | Thread.State.TIMED_WAITING | 限期等待,超时自动唤醒 | sleep()/wait(timeout)/join(timeout)/parkNanos()/parkUntil() | 超时到期或被唤醒 |
| TERMINATED | Thread.State.TERMINATED | 线程执行完毕 | run() 正常结束或异常退出 | 无(终态) |
关键认知:RUNNABLE 在 JVM 层面合并了操作系统的 Running(运行中)和 Ready(就绪等待调度)两种状态。JVM 不区分"正在执行"和"等待 CPU",统一视为可运行。
2. 线程状态转换全链路——从源码理解转换时机
┌─────────────────────────────────────────────────────────┐
│ 线程状态转换图 │
├─────────────────────────────────────────────────────────┤
│ │
│ NEW │
│ │ │
│ │ start() │
│ ▼ │
│ RUNNABLE ◄─────────────────────────────────────┐ │
│ │ │ │
│ │ 获取锁失败 │ │
│ ▼ │ │
│ BLOCKED ──► 获取锁成功 ──►──────────────────────┘ │
│ │ │ │
│ │ wait() / join() / park() │ │
│ ▼ │ │
│ WAITING ──► notify() / notifyAll() / unpark() ──┘ │
│ │ │ │
│ │ sleep(timeout) / wait(timeout) / parkNanos() │ │
│ ▼ │ │
│ TIMED_WAITING ──► 超时到期 / 被唤醒 ───────────────┘ │
│ │ │
│ │ run() 结束或异常 │
│ ▼ │
│ TERMINATED │
│ │
└─────────────────────────────────────────────────────────┘
-
2.1 NEW → RUNNABLE:线程启动
Thread t = new Thread(() -> { /* 任务 */ });
// t.getState() == NEW
t.start();
// t.getState() == RUNNABLE(可能立即被调度执行,也可能在就绪队列等待)源码层面:Thread.start() 调用 native start0(),由 JVM 创建操作系统线程并调用 run()。
-
2.2 RUNNABLE → BLOCKED:Monitor 锁竞争失败
Object lock = new Object();
Thread t = new Thread(() -> {
synchronized (lock) { // 如果 lock 已被其他线程持有
// 当前线程从 RUNNABLE → BLOCKED
// 进入 Monitor 的 _EntryList,等待锁释放
}
});底层实现:monitorenter 指令调用 ObjectMonitor::enter,如果 _owner 不为空且不是当前线程,线程进入 _EntryList 或 _cxq(竞争队列),状态变为 BLOCKED。
-
2.3 BLOCKED → RUNNABLE:获取 Monitor 锁
synchronized (lock) {
// 锁持有者执行完毕,释放锁
// JVM 调用 ObjectMonitor::exit
// 从 _EntryList/_cxq 中唤醒一个线程
// 被唤醒线程状态从 BLOCKED → RUNNABLE
} -
2.4 RUNNABLE → WAITING:主动放弃锁并等待
synchronized (lock) {
lock.wait(); // 当前线程释放锁,进入 _WaitSet,状态 → WAITING
}底层实现:Object.wait() 调用 ObjectMonitor::wait,线程被封装为 ObjectWaiter 节点加入 _WaitSet,释放 _owner 并 park 阻塞。
WAITING 的三种触发方式对比:
方法是否释放锁等待对象唤醒方式使用场景 Object.wait() ✅ 释放 Monitor 锁 对象的 Monitor notify()/notifyAll() 生产者-消费者 Thread.join() ❌ 不释放(无锁可释) 目标线程 目标线程执行完毕 等待子线程完成 LockSupport.park() ❌ 不释放(与锁无关) 当前线程 LockSupport.unpark(thread) AQS 框架阻塞 -
2.5 WAITING → RUNNABLE:被显式唤醒
synchronized (lock) {
lock.notify(); // 唤醒 _WaitSet 中的一个线程
// 被唤醒线程从 WAITING → BLOCKED(需重新竞争锁)→ RUNNABLE
}关键细节:notify() 唤醒后线程不是直接进入 RUNNABLE,而是先进入 BLOCKED 状态重新竞争锁!这是面试常见陷阱。
-
2.6 RUNNABLE → TIMED_WAITING:限时等待
// 方式1:Thread.sleep(不释放锁)
Thread.sleep(1000); // 状态 → TIMED_WAITING,超时后 → RUNNABLE// 方式2:Object.wait(timeout)(释放 Monitor 锁)
synchronized (lock) {
lock.wait(5000); // 状态 → TIMED_WAITING,超时或被 notify → BLOCKED → RUNNABLE
}// 方式3:Thread.join(timeout)(等待目标线程)
t.join(3000); // 状态 → TIMED_WAITING,超时或 t 结束 → RUNNABLE// 方式4:LockSupport.parkNanos(AQS 常用)
LockSupport.parkNanos(1000_000L); // 状态 → TIMED_WAITING,超时或被 unpark → RUNNABLEsleep vs wait 的核心区别:
特性Thread.sleep()Object.wait() 锁释放 ❌ 不释放 ✅ 释放 Monitor 锁 调用前提 任意位置 必须在 synchronized 块内 唤醒方式 只能超时到期 notify() 或超时 状态恢复 直接 → RUNNABLE 先 → BLOCKED(竞争锁)→ RUNNABLE -
2.7 TIMED_WAITING → RUNNABLE:超时或被唤醒
// 超时到期:JVM 定时器触发,线程直接 → RUNNABLE
// 被唤醒:同 WAITING,先 → BLOCKED → RUNNABLE -
2.8 RUNNABLE → TERMINATED:线程结束
Thread t = new Thread(() -> {
// run() 正常执行完毕
});
t.start();
t.join(); // 等待线程结束
// t.getState() == TERMINATED异常结束:
Thread t = new Thread(() -> {
throw new RuntimeException("异常退出");
});
// 未捕获异常 → 调用 UncaughtExceptionHandler → TERMINATED
3. JVM 线程状态 vs 操作系统线程状态
JVM 的 6 种状态是对操作系统线程状态的抽象,映射关系如下:
| NEW | 无(未创建) | 无(未创建) | 仅 Java 层对象 |
| RUNNABLE | R (running) / S (sleeping) | Running / Ready | 可能正在执行,也可能在等待调度 |
| BLOCKED | D (uninterruptible sleep) | Wait | 等待 Monitor 锁 |
| WAITING | S (interruptible sleep) | Wait | 等待条件变量 |
| TIMED_WAITING | S (interruptible sleep) | Wait | 限时等待 |
| TERMINATED | Z (zombie) / 已销毁 | Terminated | 线程已结束 |
关键认知:jstack 看到的 RUNNABLE 线程,在操作系统层面可能是 R(真正运行)或 S(可中断睡眠,等待 CPU 调度)。
4. 状态转换的源码级理解
-
4.1 Thread.sleep 的底层实现
// JVM 源码:os::sleep 方法(OpenJDK 8)
int os::sleep(Thread* thread, jlong millis, bool interruptable) {
ParkEvent * const slp = thread->_SleepEvent;
// …
if (interruptable) {
if (os::is_interrupted(thread, true)) return OS_INTRPT;
// 设置线程状态为 TIMED_WAITING
ThreadBlockInVM tbivm(thread);
slp->park(millis); // 调用 pthread_cond_timedwait
}
// …
}Thread.sleep 最终调用操作系统条件变量的限时等待(Linux 的 pthread_cond_timedwait)。
-
4.2 Object.wait 的底层实现
// ObjectMonitor::wait(OpenJDK 8)
void ObjectMonitor::wait(jlong millis, bool interruptable, TRAPS) {
// 1. 将当前线程封装为 ObjectWaiter
ObjectWaiter node(Self);
node._TState = ObjectWaiter::TS_WAIT;// 2. 加入 _WaitSet(条件队列)
AddWaiter(&node);// 3. 释放锁
exit(true, THREAD);// 4. 挂起线程(park)
if (interruptable && Thread::is_interrupted(Self, true)) {
// 处理中断
}
// …
} -
4.3 LockSupport.park 的底层实现
// Unsafe_Park(OpenJDK 8)
UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
// 调用线程的 ParkEvent
thread->parker()->park(isAbsolute, time);
UNSAFE_END// Parker::park(os/posix/vm/os_posix.cpp)
void Parker::park(bool isAbsolute, jlong time) {
if (time == 0) {
pthread_cond_wait(_cond, _mutex); // 无限等待
} else {
pthread_cond_timedwait(_cond, _mutex, &absTime); // 限时等待
}
}LockSupport.park 使用线程私有的 Parker 对象,通过 pthread_cond_wait/pthread_cond_timedwait 实现,与 Object.wait 的区别是不依赖 Monitor 锁。
5. 生产环境实战——jstack 线程状态分析
-
5.1 线程状态统计
# 查看 Java 进程中各状态线程数量
jstack <pid> | grep "java.lang.Thread.State" | sort | uniq -c# 典型输出:
# 10 java.lang.Thread.State: RUNNABLE
# 5 java.lang.Thread.State: BLOCKED
# 20 java.lang.Thread.State: WAITING (on object monitor)
# 50 java.lang.Thread.State: TIMED_WAITING (sleeping)
# 2 java.lang.Thread.State: TIMED_WAITING (parking) -
5.2 BLOCKED 线程分析——锁竞争排查
jstack <pid> | grep -A 20 "java.lang.Thread.State: BLOCKED"
# 输出示例:
# "http-nio-8080-exec-5" #25 daemon prio=5 os_prio=0 tid=0x00007f8b4c001800 nid=0x5203 waiting for monitor entry [0x000070000c5a5000]
# java.lang.Thread.State: BLOCKED (on object monitor)
# at com.example.Service.process(Service.java:45)
# – waiting to lock <0x000000076b5c7d58> (a java.lang.Object)
# – locked <0x000000076b5c7d68> (a java.lang.Object)
# "http-nio-8080-exec-3" #23 daemon prio=5 os_prio=0 tid=0x00007f8b4c000800 nid=0x5201 runnable [0x000070000c3a3000]
# java.lang.Thread.State: RUNNABLE
# at com.example.Service.process(Service.java:45)
# – locked <0x000000076b5c7d58> (a java.lang.Object)分析:线程 exec-5 等待锁 0x76b5c7d58,而 exec-3 持有该锁。如果大量线程 BLOCKED 在同一锁上,说明锁粒度过粗或持有时间过长。
-
5.3 WAITING 线程分析——死锁排查
jstack <pid> | grep -A 30 "Found one Java-level deadlock"
# 输出示例:
# Found one Java-level deadlock:
# =============================
# "Thread-1":
# waiting to lock monitor 0x00007f8b4c0032b8 (object 0x000000076b5c7d58, a java.lang.Object),
# which is held by "Thread-0"
# "Thread-0":
# waiting to lock monitor 0x00007f8b4c003518 (object 0x000000076b5c7d68, a java.lang.Object),
# which is held by "Thread-1" -
5.4 TIMED_WAITING 线程分析——连接池/超时问题
jstack <pid> | grep -B 1 -A 15 "TIMED_WAITING"
# 大量 TIMED_WAITING (on object monitor) → 可能是数据库连接池等待
# 大量 TIMED_WAITING (sleeping) → 可能是定时任务或轮询
# 大量 TIMED_WAITING (parking) → 可能是 AQS 条件等待或 Netty EventLoop
6. 常见误区澄清
| RUNNABLE = 正在运行 | ❌ RUNNABLE 包含运行中和就绪等待调度两种状态 |
| BLOCKED 和 WAITING 一样 | ❌ BLOCKED 是竞争锁失败被动阻塞;WAITING 是主动放弃锁等待条件 |
| sleep 释放锁 | ❌ Thread.sleep 不释放任何锁;Object.wait 才释放 Monitor 锁 |
| notify 后立即执行 | ❌ notify 后线程先进入 BLOCKED 重新竞争锁,不是直接运行 |
| 线程结束后可以 start() 再次启动 | ❌ TERMINATED 是终态,无法转换,再次 start() 抛 IllegalThreadStateException |
| interrupt 能中断 BLOCKED | ❌ interrupt 只能中断 WAITING/TIMED_WAITING,BLOCKED 无法被中断 |
7. 面试官追问与高分回答模板
-
追问 1:“线程有几种状态,之间如何转换?”
- 低分回答:“6 种状态,通过 start、wait、notify、sleep 等方法转换。”(没有触及底层实现)
- 高分回答:
"Java 线程有 6 种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
转换链路:
- NEW → RUNNABLE:start() 启动线程;
- RUNNABLE → BLOCKED:synchronized 竞争 Monitor 锁失败,进入 _EntryList;
- BLOCKED → RUNNABLE:锁被释放,从 _EntryList 唤醒;
- RUNNABLE → WAITING:wait()(释放锁进 _WaitSet)、join()(等待目标线程)、park()(线程私有条件变量);
- WAITING → RUNNABLE:notify()/notifyAll()(注意:唤醒后先进入 BLOCKED 重新竞争锁)、unpark()、目标线程结束;
- RUNNABLE → TIMED_WAITING:sleep()、wait(timeout)、join(timeout)、parkNanos();
- TIMED_WAITING → RUNNABLE:超时到期或被唤醒;
- RUNNABLE → TERMINATED:run() 正常结束或异常退出。
关键认知:RUNNABLE 合并了操作系统的 Running 和 Ready;notify 唤醒后线程先 BLOCKED 再 RUNNABLE,不是直接运行。"
-
追问 2:“BLOCKED 和 WAITING 有什么区别?”
- 高分回答:
"两者本质不同:
- 触发原因:BLOCKED 是被动阻塞——线程想获取锁但竞争失败,被 JVM 放入 Monitor 的 _EntryList;WAITING 是主动等待——线程已持有锁,调用 wait() 主动释放锁并进入 _WaitSet。
- 锁状态:BLOCKED 线程不持有锁;WAITING 线程在调用 wait() 前持有锁,调用后释放。
- 唤醒方式:BLOCKED 只能等锁持有者释放锁;WAITING 需要其他线程显式 notify 或超时到期。
- 中断响应:BLOCKED 无法被 interrupt 中断;WAITING 可以响应中断,抛出 InterruptedException。
类比:BLOCKED 像排队买票(还没轮到你);WAITING 像买完票去旁边休息区坐着等叫号(已放弃位置)。"
- 高分回答:
追问 3:“sleep 和 wait 的区别是什么?”
- 高分回答:
"sleep 和 wait 有五个核心区别:
- 锁释放:sleep 不释放任何锁;wait 释放当前线程持有的 Monitor 锁;
- 调用位置:sleep 任意位置可调用;wait 必须在 synchronized 块内调用(否则抛 IllegalMonitorStateException);
- 所属类:sleep 是 Thread 的静态方法;wait 是 Object 的实例方法;
- 唤醒方式:sleep 只能等超时到期;wait 可以被 notify 提前唤醒;
- 状态恢复:sleep 到期后直接 → RUNNABLE;wait 被唤醒后先 → BLOCKED(重新竞争锁)→ RUNNABLE。
设计哲学:sleep 是线程自我暂停,与锁无关;wait 是对象级别的条件等待,必须配合锁使用(生产者-消费者模式)。"
追问 4:“为什么 wait 被唤醒后要先进入 BLOCKED 而不是直接 RUNNABLE?”
- 高分回答:
"这是 JVM 的安全设计:notify 唤醒线程时,锁可能仍被 notify 调用者持有(notify 在 synchronized 块内调用,退出时才释放锁)。如果线程直接 RUNNABLE,会在锁未释放时执行,破坏互斥性。
正确流程:
- 线程 A 调用 wait() → 释放锁 → 进入 WAITING;
- 线程 B 获取锁 → 执行业务 → 调用 notify() → 线程 A 被标记为’待唤醒’;
- 线程 B 退出 synchronized → 释放锁;
- 线程 A 从 _WaitSet 移出 → 进入 _EntryList(BLOCKED)→ 竞争锁 → 获取锁 → RUNNABLE。
所以 notify 只是’通知’,不是’授权’,线程必须重新竞争锁。"
追问 5:“如何用 jstack 分析线程状态定位性能问题?”
- 高分回答:
"jstack 线程状态分析分三步:
- 状态统计:jstack <pid> | grep 'java.lang.Thread.State' | sort | uniq -c,快速识别异常状态分布;
- BLOCKED 分析:大量 BLOCKED 说明锁竞争激烈,需检查锁粒度和持有时间。用 grep -A 20 'BLOCKED' 找到锁对象和持有线程;
- WAITING/TIMED_WAITING 分析:
- WAITING (on object monitor) + at java.lang.Object.wait → 生产者-消费者等待,检查生产者是否阻塞;
- TIMED_WAITING (sleeping) + 大量线程 → 可能是轮询或定时任务,考虑改为事件驱动;
- TIMED_WAITING (parking) + at sun.misc.Unsafe.park → AQS 等待,检查锁竞争或条件等待;
- TIMED_WAITING + at java.net.SocketInputStream.socketRead0 → IO 等待,检查外部服务延迟。
实战案例:某系统 CPU 低但响应慢,jstack 发现 200 个线程中 150 个 TIMED_WAITING (parking) 在 HikariPool.getConnection,定位到数据库连接池耗尽。"
追问 6:“线程结束后还能重新 start 吗?”
- 高分回答:
"不能。线程进入 TERMINATED 状态后是终态,无法转换到任何其他状态。再次调用 start() 会抛出 IllegalThreadStateException。
原因:JVM 中线程的 eetop(操作系统线程句柄)在结束后已被释放,再次启动需要重新创建操作系统线程,而 Java 的 Thread 对象设计为一次性使用。
如果需要复用线程,应使用线程池(ThreadPoolExecutor),线程池中的线程执行完任务后回到等待状态(WAITING/TIMED_WAITING),而非 TERMINATED,可以重复接收任务。"
8. 方案选型速查表
| 线程启动 | NEW → RUNNABLE | start() | 只能调用一次 |
| 锁竞争 | RUNNABLE → BLOCKED | synchronized | 无法被 interrupt |
| 条件等待 | RUNNABLE → WAITING | wait() | 必须在 synchronized 内,释放锁 |
| 限时等待 | RUNNABLE → TIMED_WAITING | sleep()/wait(timeout) | sleep 不释放锁 |
| AQS 阻塞 | RUNNABLE → WAITING/TIMED_WAITING | LockSupport.park() | 不依赖 Monitor 锁 |
| 等待子线程 | RUNNABLE → WAITING | join() | 目标线程结束自动唤醒 |
| 线程结束 | RUNNABLE → TERMINATED | run() 结束 | 终态,不可复用 |
💡 面试官想要的满分总结:
Java 线程的 6 种状态是 JVM 对操作系统线程的抽象,理解它们必须抓住三个关键点:
状态本质:RUNNABLE 不是"正在运行",而是"可被调度"(包含 Running 和 Ready);BLOCKED 是被动锁竞争失败,WAITING 是主动条件等待;TERMINATED 是终态不可复用。
转换细节:notify 唤醒后线程先 BLOCKED 再 RUNNABLE(需重新竞争锁);sleep 不释放锁而 wait 释放锁;interrupt 只能中断 WAITING/TIMED_WAITING,BLOCKED 无法中断。
生产实战:jstack 是线程状态分析的利器——BLOCKED 多说明锁竞争,WAITING 多说明条件等待异常,TIMED_WAITING(parking) 多说明 AQS 锁或连接池问题。线程池复用线程避免 TERMINATED,是高性能系统的标配。
最后记住:线程状态不是孤立的 API 知识点,而是排查死锁、性能瓶颈、连接池耗尽等生产问题的核心线索。
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯





