📌 人工智能开发 :AI Agents 开发实践
第17题:线程有几种状态,之间如何转换?
📚 回答:
- 核心考点 : Java 线程状态是 JVM 与操作系统线程映射的核心概念。大厂面试不会只问"有哪 6 种状态",而是深入考察 JVM 线程状态与操作系统线程状态的映射关系 (
RUNNABLE包含Running和Ready)、BLOCKED、WAITING、TIMED_WAITING的底层实现差异 (Monitor 锁 vsLockSupport.parkvs 条件变量)、以及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:线程启动
javaThread t = new Thread(() -> { /* 任务 */ }); // t.getState() == NEW t.start(); // t.getState() == RUNNABLE(可能立即被调度执行,也可能在就绪队列等待)源码层面 :
Thread.start()调用native start0(),由 JVM 创建操作系统线程并调用run()。 -
2.2 RUNNABLE → BLOCKED:Monitor 锁竞争失败
javaObject 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 锁
javasynchronized (lock) { // 锁持有者执行完毕,释放锁 // JVM 调用 ObjectMonitor::exit // 从 _EntryList/_cxq 中唤醒一个线程 // 被唤醒线程状态从 BLOCKED → RUNNABLE } -
2.4 RUNNABLE → WAITING:主动放弃锁并等待
javasynchronized (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:被显式唤醒
javasynchronized (lock) { lock.notify(); // 唤醒 _WaitSet 中的一个线程 // 被唤醒线程从 WAITING → BLOCKED(需重新竞争锁)→ RUNNABLE }关键细节 :
notify()唤醒后线程不是直接进入 RUNNABLE ,而是先进入BLOCKED状态重新竞争锁!这是面试常见陷阱。 -
2.6 RUNNABLE → TIMED_WAITING:限时等待
java// 方式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:超时或被唤醒
java// 超时到期:JVM 定时器触发,线程直接 → RUNNABLE // 被唤醒:同 WAITING,先 → BLOCKED → RUNNABLE -
2.8 RUNNABLE → TERMINATED:线程结束
javaThread t = new Thread(() -> { // run() 正常执行完毕 }); t.start(); t.join(); // 等待线程结束 // t.getState() == TERMINATED异常结束:
javaThread t = new Thread(() -> { throw new RuntimeException("异常退出"); }); // 未捕获异常 → 调用 UncaughtExceptionHandler → TERMINATED
3. JVM 线程状态 vs 操作系统线程状态
JVM 的 6 种状态是对操作系统线程状态的抽象,映射关系如下:
| JVM 状态 | Linux 状态 | Windows 状态 | 说明 |
|---|---|---|---|
| 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的底层实现cpp// 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的底层实现cpp// 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的底层实现cpp// 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 线程状态统计
bash# 查看 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 线程分析------锁竞争排查
bashjstack <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 线程分析------死锁排查
bashjstack <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 线程分析------连接池/超时问题
bashjstack <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,不是直接运行。" - NEW → 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只是'通知',不是'授权',线程必须重新竞争锁。" - 线程 A 调用
- 高分回答 : "这是 JVM 的安全设计 :
-
追问 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 知识点,而是排查死锁、性能瓶颈、连接池耗尽等生产问题的核心线索。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯