【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?

📅 2026/6/16 11:38:53
【大白话说Java面试题 第117题】【并发篇】第17题:线程有几种状态,之间如何转换?
人工智能开发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 对操作系统线程状态的抽象和简化状态枚举值说明触发条件唤醒方式NEWThread.State.NEW线程已创建未启动new Thread()无只能 → RUNNABLERUNNABLEThread.State.RUNNABLE可运行含运行中 就绪等待调度start()无调度器分配 CPUBLOCKEDThread.State.BLOCKED阻塞等待 Monitor 锁synchronized竞争失败锁被释放WAITINGThread.State.WAITING无限期等待需显式唤醒wait()/join()/LockSupport.park()notify()/notifyAll()/unpark()TIMED_WAITINGThread.State.TIMED_WAITING限期等待超时自动唤醒sleep()/wait(timeout)/join(timeout)/parkNanos()/parkUntil()超时到期或被唤醒TERMINATEDThread.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线程启动ThreadtnewThread(()-{/* 任务 */});// t.getState() NEWt.start();// t.getState() RUNNABLE可能立即被调度执行也可能在就绪队列等待源码层面Thread.start()调用native start0()由 JVM 创建操作系统线程并调用run()。2.2 RUNNABLE → BLOCKEDMonitor 锁竞争失败ObjectlocknewObject();ThreadtnewThread(()-{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 锁对象的 Monitornotify()/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限时等待// 方式1Thread.sleep不释放锁Thread.sleep(1000);// 状态 → TIMED_WAITING超时后 → RUNNABLE// 方式2Object.wait(timeout)释放 Monitor 锁synchronized(lock){lock.wait(5000);// 状态 → TIMED_WAITING超时或被 notify → BLOCKED → RUNNABLE}// 方式3Thread.join(timeout)等待目标线程t.join(3000);// 状态 → TIMED_WAITING超时或 t 结束 → RUNNABLE// 方式4LockSupport.parkNanosAQS 常用LockSupport.parkNanos(1000_000L);// 状态 → TIMED_WAITING超时或被 unpark → RUNNABLEsleep vs wait 的核心区别特性Thread.sleep()Object.wait()锁释放❌ 不释放✅ 释放 Monitor 锁调用前提任意位置必须在synchronized块内唤醒方式只能超时到期notify()或超时状态恢复直接 → RUNNABLE先 → BLOCKED竞争锁→ RUNNABLE2.7 TIMED_WAITING → RUNNABLE超时或被唤醒// 超时到期JVM 定时器触发线程直接 → RUNNABLE// 被唤醒同 WAITING先 → BLOCKED → RUNNABLE2.8 RUNNABLE → TERMINATED线程结束ThreadtnewThread(()-{// run() 正常执行完毕});t.start();t.join();// 等待线程结束// t.getState() TERMINATED异常结束ThreadtnewThread(()-{thrownewRuntimeException(异常退出);});// 未捕获异常 → 调用 UncaughtExceptionHandler → TERMINATED3. JVM 线程状态 vs 操作系统线程状态JVM 的 6 种状态是对操作系统线程状态的抽象映射关系如下JVM 状态Linux 状态Windows 状态说明NEW无未创建无未创建仅 Java 层对象RUNNABLER (running)/S (sleeping)Running/Ready可能正在执行也可能在等待调度BLOCKEDD (uninterruptible sleep)Wait等待 Monitor 锁WAITINGS (interruptible sleep)Wait等待条件变量TIMED_WAITINGS (interruptible sleep)Wait限时等待TERMINATEDZ (zombie)/ 已销毁Terminated线程已结束关键认知jstack看到的RUNNABLE线程在操作系统层面可能是R真正运行或S可中断睡眠等待 CPU 调度。4. 状态转换的源码级理解4.1Thread.sleep的底层实现// JVM 源码os::sleep 方法OpenJDK 8intos::sleep(Thread*thread,jlong millis,boolinterruptable){ParkEvent*constslpthread-_SleepEvent;// ...if(interruptable){if(os::is_interrupted(thread,true))returnOS_INTRPT;// 设置线程状态为 TIMED_WAITINGThreadBlockInVMtbivm(thread);slp-park(millis);// 调用 pthread_cond_timedwait}// ...}Thread.sleep最终调用操作系统条件变量的限时等待Linux 的pthread_cond_timedwait。4.2Object.wait的底层实现// ObjectMonitor::waitOpenJDK 8voidObjectMonitor::wait(jlong millis,boolinterruptable,TRAPS){// 1. 将当前线程封装为 ObjectWaiterObjectWaiternode(Self);node._TStateObjectWaiter::TS_WAIT;// 2. 加入 _WaitSet条件队列AddWaiter(node);// 3. 释放锁exit(true,THREAD);// 4. 挂起线程parkif(interruptableThread::is_interrupted(Self,true)){// 处理中断}// ...}4.3LockSupport.park的底层实现// Unsafe_ParkOpenJDK 8UNSAFE_ENTRY(void,Unsafe_Park(JNIEnv*env,jobject unsafe,jboolean isAbsolute,jlong time))// 调用线程的 ParkEventthread-parker()-park(isAbsolute,time);UNSAFE_END// Parker::parkos/posix/vm/os_posix.cppvoidParker::park(boolisAbsolute,jlong time){if(time0){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 进程中各状态线程数量jstackpid|grepjava.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 线程分析——锁竞争排查jstackpid|grep-A20java.lang.Thread.State: BLOCKED# 输出示例# http-nio-8080-exec-5 #25 daemon prio5 os_prio0 tid0x00007f8b4c001800 nid0x5203 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 prio5 os_prio0 tid0x00007f8b4c000800 nid0x5201 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 线程分析——死锁排查jstackpid|grep-A30Found 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-15.4 TIMED_WAITING 线程分析——连接池/超时问题jstackpid|grep-B1-A15TIMED_WAITING# 大量 TIMED_WAITING (on object monitor) → 可能是数据库连接池等待# 大量 TIMED_WAITING (sleeping) → 可能是定时任务或轮询# 大量 TIMED_WAITING (parking) → 可能是 AQS 条件等待或 Netty EventLoop6. 常见误区澄清误区正确理解RUNNABLE 正在运行❌RUNNABLE包含运行中和就绪等待调度两种状态BLOCKED和WAITING一样❌BLOCKED是竞争锁失败被动阻塞WAITING是主动放弃锁等待条件sleep释放锁❌Thread.sleep不释放任何锁Object.wait才释放 Monitor 锁notify后立即执行❌notify后线程先进入BLOCKED重新竞争锁不是直接运行线程结束后可以start()再次启动❌TERMINATED是终态无法转换再次start()抛IllegalThreadStateExceptioninterrupt能中断BLOCKED❌interrupt只能中断WAITING/TIMED_WAITINGBLOCKED无法被中断7. 面试官追问与高分回答模板追问 1“线程有几种状态之间如何转换”低分回答“6 种状态通过 start、wait、notify、sleep 等方法转换。”没有触及底层实现高分回答Java 线程有 6 种状态NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。转换链路NEW → RUNNABLEstart()启动线程RUNNABLE → BLOCKEDsynchronized竞争 Monitor 锁失败进入_EntryListBLOCKED → RUNNABLE锁被释放从_EntryList唤醒RUNNABLE → WAITINGwait()释放锁进_WaitSet、join()等待目标线程、park()线程私有条件变量WAITING → RUNNABLEnotify()/notifyAll()注意唤醒后先进入 BLOCKED 重新竞争锁、unpark()、目标线程结束RUNNABLE → TIMED_WAITINGsleep()、wait(timeout)、join(timeout)、parkNanos()TIMED_WAITING → RUNNABLE超时到期或被唤醒RUNNABLE → TERMINATEDrun()正常结束或异常退出。关键认知RUNNABLE合并了操作系统的Running和Readynotify唤醒后线程先BLOCKED再RUNNABLE不是直接运行。追问 2“BLOCKED 和 WAITING 有什么区别”高分回答两者本质不同触发原因BLOCKED是被动阻塞——线程想获取锁但竞争失败被 JVM 放入 Monitor 的_EntryListWAITING是主动等待——线程已持有锁调用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到期后直接 → RUNNABLEwait被唤醒后先 → BLOCKED重新竞争锁→ RUNNABLE。设计哲学sleep是线程自我暂停与锁无关wait是对象级别的条件等待必须配合锁使用生产者-消费者模式。追问 4“为什么 wait 被唤醒后要先进入 BLOCKED 而不是直接 RUNNABLE”高分回答这是 JVM 的安全设计notify唤醒线程时锁可能仍被notify调用者持有notify在synchronized块内调用退出时才释放锁。如果线程直接 RUNNABLE会在锁未释放时执行破坏互斥性。正确流程线程 A 调用wait()→ 释放锁 → 进入 WAITING线程 B 获取锁 → 执行业务 → 调用notify()→ 线程 A 被标记为’待唤醒’线程 B 退出synchronized→ 释放锁线程 A 从_WaitSet移出 → 进入_EntryListBLOCKED→ 竞争锁 → 获取锁 → 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_WAITINGat 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 → RUNNABLEstart()只能调用一次锁竞争RUNNABLE → BLOCKEDsynchronized无法被 interrupt条件等待RUNNABLE → WAITINGwait()必须在 synchronized 内释放锁限时等待RUNNABLE → TIMED_WAITINGsleep()/wait(timeout)sleep 不释放锁AQS 阻塞RUNNABLE → WAITING/TIMED_WAITINGLockSupport.park()不依赖 Monitor 锁等待子线程RUNNABLE → WAITINGjoin()目标线程结束自动唤醒线程结束RUNNABLE → TERMINATEDrun()结束终态不可复用面试官想要的满分总结Java 线程的 6 种状态是 JVM 对操作系统线程的抽象理解它们必须抓住三个关键点状态本质RUNNABLE不是正在运行而是可被调度包含 Running 和 ReadyBLOCKED是被动锁竞争失败WAITING是主动条件等待TERMINATED是终态不可复用。转换细节notify唤醒后线程先BLOCKED再RUNNABLE需重新竞争锁sleep不释放锁而wait释放锁interrupt只能中断WAITING/TIMED_WAITINGBLOCKED无法中断。生产实战jstack是线程状态分析的利器——BLOCKED 多说明锁竞争WAITING 多说明条件等待异常TIMED_WAITING(parking) 多说明 AQS 锁或连接池问题。线程池复用线程避免 TERMINATED是高性能系统的标配。最后记住线程状态不是孤立的 API 知识点而是排查死锁、性能瓶颈、连接池耗尽等生产问题的核心线索。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~