1. 项目概述为什么我们需要关心线程的“心电图”如果你写过一段Java并发代码然后发现它时而流畅运行时而卡死不动或者CPU占用率莫名其妙地飙升那你大概率已经和“线程状态”打过照面了。这玩意儿就像是线程的“心电图”实时描绘着它的生命活动——是在勤奋工作还是在等待资源抑或是已经寿终正寝。很多开发者尤其是刚接触并发的朋友往往只关注synchronized、Lock或者volatile这些“高级”工具却忽略了最基础的线程状态转换。结果就是当程序出现死锁、活锁、资源耗尽或者响应迟缓时排查起来如同大海捞针因为你不清楚你的线程此刻究竟在哪个“状态”里“思考人生”。“在Java中的线程状态转换”这个主题恰恰是解开并发疑难杂症的钥匙。它不是一个枯燥的理论知识点而是一套贯穿于Thread类源码、JVM实现以及操作系统调度底层的动态规则。理解它意味着你能精准诊断通过jstack等工具抓取的线程堆栈你能一眼看出哪些线程在BLOCKED阻塞等待锁哪些在WAITING等待条件而不是瞎猜。合理设计知道调用Object.wait()、Thread.join()或LockSupport.park()会引发怎样的状态变迁从而避免设计出容易导致死锁或线程泄漏的协作逻辑。高效调优分析出线程大量时间处在WAITING或TIMED_WAITING状态是否是预期的如等待I/O还是设计缺陷如锁竞争过于激烈从而有针对性地进行优化。简单说搞懂线程状态转换是你从“能写并发代码”迈向“能写好、能调优并发代码”的必经之路。无论你是正在处理高并发的后端工程师还是想要深入理解JVM的探索者这份“心电图”解读指南都值得你花时间彻底掌握。接下来我们就抛开教科书式的定义从源码和实战的角度把这六种状态和它们之间的转换路径掰开揉碎了讲清楚。2. 线程状态的六种“人生阶段”深度解析Java语言通过java.lang.Thread.State这个枚举类明确定义了一个线程在其生命周期中可能处于的六种状态。很多人背过这六个名字但理解并不深刻。我们结合Thread类的源码和JVM的行为来重新认识它们。2.1 NEW新生蓝图已就绪引擎未启动当你执行Thread thread new Thread(() - {...})时这个thread对象就处于NEW状态。此时操作系统级别的线程或说内核线程并未被创建。JVM只是在堆内存中分配了一个Thread对象初始化了它的成员变量如target-Runnable任务、name-线程名等。你可以把它理解为一辆设计图纸完备、零件齐全的跑车但还没有组装下线更没有点火发动。注意处于NEW状态的线程调用除start()之外的任何方法如interrupt()、join()基本都是无意义的甚至可能抛出IllegalThreadStateException。它的生命始于start()。2.2 RUNNABLE可运行整装待发争夺CPU时间片这是最容易被误解的状态。很多人把它等同于“正在运行”Running。但在JVM的规范里RUNNABLE状态意味着线程已经准备好执行正在等待CPU资源。它包含了两种子情况Ready就绪线程已经满足了所有执行条件如锁已获取、资源已就位正待在操作系统的就绪队列里等着操作系统调度器把CPU时间片分配给它。Running运行中线程已经获取了CPU时间片正在执行其run()方法中的代码。为什么JVM不把它们分开因为“是否正在占用CPU”这个信息瞬息万变完全由操作系统调度器决定JVM层面很难也无必要实时精确区分。所以RUNNABLE是一个宏观的、面向JVM的状态。当你看到线程处于此状态就知道它要么在跑要么随时可以跑没有在等待任何会导致其挂起的条件如I/O、锁、休眠。2.3 BLOCKED阻塞在同步的门口排队这是由同步互斥引发的等待状态。当一个线程试图进入一个synchronized方法或代码块但该同步锁正被其他线程持有那么该线程就会进入BLOCKED状态。关键点只与synchronized关键字相关。这是BLOCKED状态的唯一入口。对于java.util.concurrent.locks.Lock接口的实现如ReentrantLock线程在获取锁失败时会进入WAITING或TIMED_WAITING状态而非BLOCKED。这是很多人的知识盲区。被动等待。线程自身无法主动控制何时恢复完全依赖于持有锁的线程释放锁。JVM监控使用jstack或JConsole等工具时如果看到大量线程处于BLOCKED状态并且等待的是同一个锁对象这就是一个强烈的信号表明此处可能存在激烈的锁竞争是性能瓶颈的潜在点。2.4 WAITING无限期等待主动让出CPU等待“唤醒信号”线程进入WAITING状态意味着它主动放弃了当前持有的CPU时间片和锁如果有的话并等待另一个线程发出特定的“通知”来唤醒它。这是一个无限期的等待没有超时时间。触发WAITING状态的三种核心方法Object.wait(): 线程必须先持有该对象的监视器锁即在synchronized块内。调用wait()后它会释放锁并进入WAITING状态直到其他线程调用同一个对象的notify()或notifyAll()。Thread.join(): 比如在线程A中调用threadB.join()线程A会进入WAITING状态直到线程B运行终止。LockSupport.park():java.util.concurrent包中锁框架的底层支撑方法。调用后当前线程会禁用线程调度进入等待状态直到获得“许可”通过LockSupport.unpark(Thread)。WAITING状态是线程间协作的基础。它的核心思想是“条件不满足我就先睡会儿你好了叫我”。2.5 TIMED_WAITING限期等待带闹钟的等待此状态与WAITING本质相同都是主动放弃CPU进行等待。唯一的区别是带了超时参数。如果在指定的时间内没有被唤醒线程将自动恢复到RUNNABLE状态去竞争CPU资源。这提供了避免无限期等待导致程序“假死”的能力。常见触发方法Thread.sleep(long millis): 让当前线程休眠指定毫秒数。注意sleep不会释放任何持有的锁。Object.wait(long timeout): 带超时的wait。Thread.join(long millis): 带超时的join。LockSupport.parkNanos(long nanos)/LockSupport.parkUntil(long deadline): 带超时的park。在诊断时看到TIMED_WAITING状态通常比看到WAITING更让人安心一些因为它意味着有超时保底。但大量线程处于此状态也可能意味着任务调度或资源等待超时设置不合理。2.6 TERMINATED终止任务完成生命终结线程的run()方法正常执行完毕或者因为未捕获的异常而退出线程就进入TERMINATED状态。一旦进入此状态线程的生命周期就结束了不能再通过start()方法重启会抛异常。这个状态下的线程对象实例还在可以作为垃圾被回收但它代表的执行流已经消亡。3. 状态转换图谱与核心路径详解理解了六个状态的定义我们来看它们是如何动态转换的。下面这张“心电图”全景图揭示了线程一生的主要轨迹[NEW] --start()-- [RUNNABLE] [RUNNABLE] --竞争synchronized锁失败-- [BLOCKED] [RUNNABLE] --调用wait()/join()/park()-- [WAITING] [RUNNABLE] --调用sleep()/wait(timeout)/join(timeout)/parkNanos()-- [TIMED_WAITING] [BLOCKED] --获取到synchronized锁-- [RUNNABLE] [WAITING] --被notify()/notifyAll()/目标线程终止/unpark()-- [RUNNABLE] [TIMED_WAITING] --超时或被唤醒-- [RUNNABLE] [RUNNABLE] --run()方法执行完毕或异常退出-- [TERMINATED]3.1 从NEW到RUNNABLE唯一的启动路径转换条件调用线程对象的start()方法。底层发生了什么start()方法是一个native方法。JVM会调用底层操作系统的接口如POSIX的pthread_create创建一个新的系统线程并将其与当前Thread对象关联。然后这个新创建的系统线程会去执行Thread对象的run()方法。这里有个关键细节start()方法内部会检查线程状态如果状态不是NEW就会抛出IllegalThreadStateException。这就是为什么一个线程不能start()两次。3.2 RUNNABLE与BLOCKED的互转同步锁的博弈RUNNABLE - BLOCKED: 线程尝试进入一个synchronized保护的临界区但该临界区的锁对象监视器正被其他线程持有。此时JVM会将此线程放入该锁的入口集Entry Set并将其状态设为BLOCKED。BLOCKED - RUNNABLE: 当持有锁的线程退出临界区释放锁JVM会从该锁的入口集中选择一个线程选择策略取决于JVM实现不一定是公平的将其状态从BLOCKED改为RUNNABLE并赋予其锁的所有权使其得以继续执行。实操心得在分析死锁时BLOCKED状态是核心线索。如果线程A持有锁L1状态为BLOCKED等待锁L2而线程B持有锁L2状态为BLOCKED等待锁L1一个经典的死锁就形成了。jstack输出的信息会清晰地显示每个BLOCKED线程在等待哪个锁waiting to lock 0x000000071ab8c4b0以及它当前持有哪个锁locked 0x000000071ab8c4a0。3.3 RUNNABLE与WAITING/TIMED_WAITING的互转主动协作的艺术这是线程间通信和协作的核心区域转换由特定的API调用触发。进入等待Object.wait(): 前提是持有对象锁。调用后线程释放锁并进入该对象的等待集Wait Set状态变为WAITING或TIMED_WAITING。Thread.join(): 底层是通过Object.wait()在目标线程对象上实现的。调用线程会等待目标线程死亡。LockSupport.park(): 更底层的机制不依赖于对象监视器是AQSAbstractQueuedSynchronizer等高级同步器的基础。从等待中恢复对于wait(): 需要其他线程调用同一个对象的notify()或notifyAll()。被唤醒后线程需要重新竞争该对象的锁获取到锁之后才能从wait()调用处返回状态变回RUNNABLE。对于join(): 当目标线程运行结束进入TERMINATED时JVM会自动调用该线程对象的notifyAll()唤醒所有等待它的线程。对于park(): 需要其他线程调用LockSupport.unpark(thisThread)来唤醒。重要提示从WAITING/TIMED_WAITING被唤醒后线程是回到RUNNABLE状态去竞争资源如锁而不是直接恢复执行。这中间存在一个“竞争窗口期”。这也是为什么Object.wait()的调用必须放在while循环中检查条件而不能用if因为存在“虚假唤醒”spurious wakeup的可能性——即线程在没有收到notify的情况下也可能被唤醒。3.4 从任何状态到TERMINATED生命的终点当线程的run()方法执行到最后一个}或者方法中抛出了一个未捕获的异常并且该异常没有被Thread.UncaughtExceptionHandler处理线程就会进入TERMINATED状态。这是一个单向的、不可逆的转换。即使你还有对这个Thread对象的引用它也不再代表一个可以执行的实体。4. 实战通过jstack诊断线程状态问题理论说再多不如看个实战。假设我们有一个Web应用在压测时发现吞吐量上不去偶尔还有请求超时。我们可以通过jstack工具来抓取线程转储Thread Dump进行分析。首先找到Java进程的PID然后执行jstack -l PID thread_dump.log打开thread_dump.log你会看到很多如下格式的线程信息http-nio-8080-exec-1 #31 daemon prio5 os_prio31 tid0x00007fe0c5119000 nid0x9a03 waiting on condition [0x0000700008bb9000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for 0x000000076b4155c8 (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) at java.util.concurrent.ArrayBlockingQueue.poll(ArrayBlockingQueue.java:418) ... // 我们的业务调用栈 Thread-2 #18 prio5 os_prio31 tid0x00007fe0c5818800 nid0x6703 waiting for monitor entry [0x00007000093b6000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Service.process(Service.java:42) - waiting to lock 0x000000076b1a8b80 (a com.example.Resource) at com.example.Controller.handleRequest(Controller.java:33) ... Thread-3 #19 prio5 os_prio31 tid0x00007fe0c5819000 nid0x6803 runnable [0x00007000094b9000] java.lang.Thread.State: RUNNABLE at com.example.Service.calculate(Service.java:55) - locked 0x000000076b1a8b80 (a com.example.Resource) ...如何解读线程1 (http-nio-8080-exec-1)状态是TIMED_WAITING原因是parking正在等待一个ConditionObject。这通常是使用了LinkedBlockingQueue、ThreadPoolExecutor等工作队列线程在等待任务到来。这是正常的I/O线程行为。线程2 (Thread-2)状态是BLOCKED原因是waiting for monitor entry明确指出了它在等待锁0x000000076b1a8b80。这是一个危险信号说明存在锁竞争。我们需要看是谁持有了这把锁。线程3 (Thread-3)状态是RUNNABLE并且它locked了线程2正在等待的同一个对象0x000000076b1a8b80死锁或严重锁竞争的嫌疑极大。我们需要进一步分析线程3的调用栈看它为什么长时间持有锁不释放是在进行复杂计算还是在等待其他资源。通过这样分析我们就能快速定位到性能瓶颈或死锁的根源——是某个同步块内的代码执行过慢还是锁的粒度设计得太粗。5. 高级话题状态转换中的陷阱与最佳实践理解了基本转换后我们再看几个容易踩坑的高级场景。5.1 interrupt() 中断机制对状态的影响Thread.interrupt()方法并不会直接强制停止一个线程它的作用是设置线程的中断标志位并尝试唤醒处于等待状态的线程。对RUNNABLE线程仅仅设置中断标志位线程需要自己通过Thread.interrupted()或isInterrupted()来检查并决定如何处理例如优雅地结束循环。对WAITING或TIMED_WAITING线程例如在sleep(),wait(),join(),park()中线程会被立即唤醒并抛出InterruptedException。同时其中断标志位会被清除在抛出异常前。这是实现线程可取消任务的关键机制。对BLOCKED线程调用interrupt()几乎没有任何即时效果因为它正在被动等待锁不响应中断。只有当它成功获取锁并从synchronized块中出来后你才能检查到中断标志位。最佳实践对于可能阻塞的任务一定要妥善处理InterruptedException。通常的做法是捕获异常在清理资源后要么直接返回要么再次设置中断标志位Thread.currentThread().interrupt()让上层代码感知而不是生吞掉异常。5.2 锁与状态的区别synchronized vs Lock这是另一个关键点。我们之前提到BLOCKED状态只对应synchronized。synchronized是JVM内置的监视器锁。竞争失败线程状态变为BLOCKED。ReentrantLock等显式锁当调用lock()方法获取锁失败时线程会进入WAITING或TIMED_WAITING状态因为其底层AQS使用了LockSupport.park()。在jstack中你会看到线程状态是WAITING (parking)而不是BLOCKED。这意味着使用jstack分析时如果看到大量WAITING (parking)且与锁相关很可能你的程序正在使用java.util.concurrent包下的显式锁。显式锁通常提供了更灵活的功能如可中断、可超时、公平锁等但也需要开发者更小心地管理锁的获取和释放必须在finally块中unlock()。5.3 线程状态与CPU资源消耗RUNNABLE状态的线程会参与CPU时间片的竞争消耗CPU资源。BLOCKED、WAITING、TIMED_WAITING状态的线程不消耗CPU除了极少数自旋锁实现的优化场景。它们被操作系统挂起等待特定事件。一个负载健康的服务器应用其线程大部分时间应处于WAITING/TIMED_WAITING等待网络I/O、任务队列或RUNNABLE合理计算状态。如果BLOCKED状态的线程比例异常高就是锁竞争激烈的明确信号。6. 常见问题排查与性能调优思路结合线程状态我们可以形成一套系统的并发问题排查方法。问题1应用响应慢CPU使用率却不高。排查思路用jstack抓取多次dump对比分析。很可能大量线程处于WAITING或TIMED_WAITING状态在等待数据库连接、外部API响应、或某个慢速资源。重点检查线程栈中等待的条件如ConditionObject、FutureTask.get()以及网络I/O调用。调优方向优化慢查询、增加连接池大小、为外部调用设置合理的超时、使用异步非阻塞IO如NIO。问题2CPU使用率持续100%但吞吐量很低。排查思路同样使用jstack。可能发现大量线程处于RUNNABLE状态并且调用栈停留在某个循环计算或密集的同步块内。也可能存在大量BLOCKED线程它们在“空转”地竞争锁虽然状态是BLOCKED不消耗CPU但那些持有锁的RUNNABLE线程可能在做无意义的忙等或死循环。调优方向优化算法复杂度、减小锁的粒度将一个大锁拆分成多个小锁、使用无锁数据结构如ConcurrentHashMap、检查是否有死循环。问题3应用运行一段时间后线程数暴涨内存溢出。排查思路检查是否有大量TERMINATED状态的线程对象未被回收这通常不是问题根源或者更关键的是检查是否有大量WAITING状态的线程永远无法被唤醒。例如生产者线程停止了但消费者线程还在take()一个空队列上无限等待。这属于线程泄漏。调优方向使用线程池管理线程生命周期、为等待操作设置超时、确保资源清理逻辑如关闭连接、取消任务能被正确执行。问题4如何确认死锁排查思路jstack工具本身具备死锁检测功能。在dump文件的最后通常会有一个专门的Found one Java-level deadlock:部分清晰地列出相互等待的线程和锁资源。即使没有这个部分通过手动分析BLOCKED线程的waiting to lock和locked信息也能找出循环等待链。掌握线程状态转换就等于为你的并发程序装上了最细致的监控探头。它不能直接解决所有问题但它能为你提供最准确、最直接的线索让你在复杂的多线程世界里不再盲目调试而是有的放矢。下次当你面对一个行为诡异的并发程序时别犹豫先抓一份线程转储从解读这些状态开始吧。
Java线程状态转换:从源码到实战的并发诊断指南
1. 项目概述为什么我们需要关心线程的“心电图”如果你写过一段Java并发代码然后发现它时而流畅运行时而卡死不动或者CPU占用率莫名其妙地飙升那你大概率已经和“线程状态”打过照面了。这玩意儿就像是线程的“心电图”实时描绘着它的生命活动——是在勤奋工作还是在等待资源抑或是已经寿终正寝。很多开发者尤其是刚接触并发的朋友往往只关注synchronized、Lock或者volatile这些“高级”工具却忽略了最基础的线程状态转换。结果就是当程序出现死锁、活锁、资源耗尽或者响应迟缓时排查起来如同大海捞针因为你不清楚你的线程此刻究竟在哪个“状态”里“思考人生”。“在Java中的线程状态转换”这个主题恰恰是解开并发疑难杂症的钥匙。它不是一个枯燥的理论知识点而是一套贯穿于Thread类源码、JVM实现以及操作系统调度底层的动态规则。理解它意味着你能精准诊断通过jstack等工具抓取的线程堆栈你能一眼看出哪些线程在BLOCKED阻塞等待锁哪些在WAITING等待条件而不是瞎猜。合理设计知道调用Object.wait()、Thread.join()或LockSupport.park()会引发怎样的状态变迁从而避免设计出容易导致死锁或线程泄漏的协作逻辑。高效调优分析出线程大量时间处在WAITING或TIMED_WAITING状态是否是预期的如等待I/O还是设计缺陷如锁竞争过于激烈从而有针对性地进行优化。简单说搞懂线程状态转换是你从“能写并发代码”迈向“能写好、能调优并发代码”的必经之路。无论你是正在处理高并发的后端工程师还是想要深入理解JVM的探索者这份“心电图”解读指南都值得你花时间彻底掌握。接下来我们就抛开教科书式的定义从源码和实战的角度把这六种状态和它们之间的转换路径掰开揉碎了讲清楚。2. 线程状态的六种“人生阶段”深度解析Java语言通过java.lang.Thread.State这个枚举类明确定义了一个线程在其生命周期中可能处于的六种状态。很多人背过这六个名字但理解并不深刻。我们结合Thread类的源码和JVM的行为来重新认识它们。2.1 NEW新生蓝图已就绪引擎未启动当你执行Thread thread new Thread(() - {...})时这个thread对象就处于NEW状态。此时操作系统级别的线程或说内核线程并未被创建。JVM只是在堆内存中分配了一个Thread对象初始化了它的成员变量如target-Runnable任务、name-线程名等。你可以把它理解为一辆设计图纸完备、零件齐全的跑车但还没有组装下线更没有点火发动。注意处于NEW状态的线程调用除start()之外的任何方法如interrupt()、join()基本都是无意义的甚至可能抛出IllegalThreadStateException。它的生命始于start()。2.2 RUNNABLE可运行整装待发争夺CPU时间片这是最容易被误解的状态。很多人把它等同于“正在运行”Running。但在JVM的规范里RUNNABLE状态意味着线程已经准备好执行正在等待CPU资源。它包含了两种子情况Ready就绪线程已经满足了所有执行条件如锁已获取、资源已就位正待在操作系统的就绪队列里等着操作系统调度器把CPU时间片分配给它。Running运行中线程已经获取了CPU时间片正在执行其run()方法中的代码。为什么JVM不把它们分开因为“是否正在占用CPU”这个信息瞬息万变完全由操作系统调度器决定JVM层面很难也无必要实时精确区分。所以RUNNABLE是一个宏观的、面向JVM的状态。当你看到线程处于此状态就知道它要么在跑要么随时可以跑没有在等待任何会导致其挂起的条件如I/O、锁、休眠。2.3 BLOCKED阻塞在同步的门口排队这是由同步互斥引发的等待状态。当一个线程试图进入一个synchronized方法或代码块但该同步锁正被其他线程持有那么该线程就会进入BLOCKED状态。关键点只与synchronized关键字相关。这是BLOCKED状态的唯一入口。对于java.util.concurrent.locks.Lock接口的实现如ReentrantLock线程在获取锁失败时会进入WAITING或TIMED_WAITING状态而非BLOCKED。这是很多人的知识盲区。被动等待。线程自身无法主动控制何时恢复完全依赖于持有锁的线程释放锁。JVM监控使用jstack或JConsole等工具时如果看到大量线程处于BLOCKED状态并且等待的是同一个锁对象这就是一个强烈的信号表明此处可能存在激烈的锁竞争是性能瓶颈的潜在点。2.4 WAITING无限期等待主动让出CPU等待“唤醒信号”线程进入WAITING状态意味着它主动放弃了当前持有的CPU时间片和锁如果有的话并等待另一个线程发出特定的“通知”来唤醒它。这是一个无限期的等待没有超时时间。触发WAITING状态的三种核心方法Object.wait(): 线程必须先持有该对象的监视器锁即在synchronized块内。调用wait()后它会释放锁并进入WAITING状态直到其他线程调用同一个对象的notify()或notifyAll()。Thread.join(): 比如在线程A中调用threadB.join()线程A会进入WAITING状态直到线程B运行终止。LockSupport.park():java.util.concurrent包中锁框架的底层支撑方法。调用后当前线程会禁用线程调度进入等待状态直到获得“许可”通过LockSupport.unpark(Thread)。WAITING状态是线程间协作的基础。它的核心思想是“条件不满足我就先睡会儿你好了叫我”。2.5 TIMED_WAITING限期等待带闹钟的等待此状态与WAITING本质相同都是主动放弃CPU进行等待。唯一的区别是带了超时参数。如果在指定的时间内没有被唤醒线程将自动恢复到RUNNABLE状态去竞争CPU资源。这提供了避免无限期等待导致程序“假死”的能力。常见触发方法Thread.sleep(long millis): 让当前线程休眠指定毫秒数。注意sleep不会释放任何持有的锁。Object.wait(long timeout): 带超时的wait。Thread.join(long millis): 带超时的join。LockSupport.parkNanos(long nanos)/LockSupport.parkUntil(long deadline): 带超时的park。在诊断时看到TIMED_WAITING状态通常比看到WAITING更让人安心一些因为它意味着有超时保底。但大量线程处于此状态也可能意味着任务调度或资源等待超时设置不合理。2.6 TERMINATED终止任务完成生命终结线程的run()方法正常执行完毕或者因为未捕获的异常而退出线程就进入TERMINATED状态。一旦进入此状态线程的生命周期就结束了不能再通过start()方法重启会抛异常。这个状态下的线程对象实例还在可以作为垃圾被回收但它代表的执行流已经消亡。3. 状态转换图谱与核心路径详解理解了六个状态的定义我们来看它们是如何动态转换的。下面这张“心电图”全景图揭示了线程一生的主要轨迹[NEW] --start()-- [RUNNABLE] [RUNNABLE] --竞争synchronized锁失败-- [BLOCKED] [RUNNABLE] --调用wait()/join()/park()-- [WAITING] [RUNNABLE] --调用sleep()/wait(timeout)/join(timeout)/parkNanos()-- [TIMED_WAITING] [BLOCKED] --获取到synchronized锁-- [RUNNABLE] [WAITING] --被notify()/notifyAll()/目标线程终止/unpark()-- [RUNNABLE] [TIMED_WAITING] --超时或被唤醒-- [RUNNABLE] [RUNNABLE] --run()方法执行完毕或异常退出-- [TERMINATED]3.1 从NEW到RUNNABLE唯一的启动路径转换条件调用线程对象的start()方法。底层发生了什么start()方法是一个native方法。JVM会调用底层操作系统的接口如POSIX的pthread_create创建一个新的系统线程并将其与当前Thread对象关联。然后这个新创建的系统线程会去执行Thread对象的run()方法。这里有个关键细节start()方法内部会检查线程状态如果状态不是NEW就会抛出IllegalThreadStateException。这就是为什么一个线程不能start()两次。3.2 RUNNABLE与BLOCKED的互转同步锁的博弈RUNNABLE - BLOCKED: 线程尝试进入一个synchronized保护的临界区但该临界区的锁对象监视器正被其他线程持有。此时JVM会将此线程放入该锁的入口集Entry Set并将其状态设为BLOCKED。BLOCKED - RUNNABLE: 当持有锁的线程退出临界区释放锁JVM会从该锁的入口集中选择一个线程选择策略取决于JVM实现不一定是公平的将其状态从BLOCKED改为RUNNABLE并赋予其锁的所有权使其得以继续执行。实操心得在分析死锁时BLOCKED状态是核心线索。如果线程A持有锁L1状态为BLOCKED等待锁L2而线程B持有锁L2状态为BLOCKED等待锁L1一个经典的死锁就形成了。jstack输出的信息会清晰地显示每个BLOCKED线程在等待哪个锁waiting to lock 0x000000071ab8c4b0以及它当前持有哪个锁locked 0x000000071ab8c4a0。3.3 RUNNABLE与WAITING/TIMED_WAITING的互转主动协作的艺术这是线程间通信和协作的核心区域转换由特定的API调用触发。进入等待Object.wait(): 前提是持有对象锁。调用后线程释放锁并进入该对象的等待集Wait Set状态变为WAITING或TIMED_WAITING。Thread.join(): 底层是通过Object.wait()在目标线程对象上实现的。调用线程会等待目标线程死亡。LockSupport.park(): 更底层的机制不依赖于对象监视器是AQSAbstractQueuedSynchronizer等高级同步器的基础。从等待中恢复对于wait(): 需要其他线程调用同一个对象的notify()或notifyAll()。被唤醒后线程需要重新竞争该对象的锁获取到锁之后才能从wait()调用处返回状态变回RUNNABLE。对于join(): 当目标线程运行结束进入TERMINATED时JVM会自动调用该线程对象的notifyAll()唤醒所有等待它的线程。对于park(): 需要其他线程调用LockSupport.unpark(thisThread)来唤醒。重要提示从WAITING/TIMED_WAITING被唤醒后线程是回到RUNNABLE状态去竞争资源如锁而不是直接恢复执行。这中间存在一个“竞争窗口期”。这也是为什么Object.wait()的调用必须放在while循环中检查条件而不能用if因为存在“虚假唤醒”spurious wakeup的可能性——即线程在没有收到notify的情况下也可能被唤醒。3.4 从任何状态到TERMINATED生命的终点当线程的run()方法执行到最后一个}或者方法中抛出了一个未捕获的异常并且该异常没有被Thread.UncaughtExceptionHandler处理线程就会进入TERMINATED状态。这是一个单向的、不可逆的转换。即使你还有对这个Thread对象的引用它也不再代表一个可以执行的实体。4. 实战通过jstack诊断线程状态问题理论说再多不如看个实战。假设我们有一个Web应用在压测时发现吞吐量上不去偶尔还有请求超时。我们可以通过jstack工具来抓取线程转储Thread Dump进行分析。首先找到Java进程的PID然后执行jstack -l PID thread_dump.log打开thread_dump.log你会看到很多如下格式的线程信息http-nio-8080-exec-1 #31 daemon prio5 os_prio31 tid0x00007fe0c5119000 nid0x9a03 waiting on condition [0x0000700008bb9000] java.lang.Thread.State: TIMED_WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for 0x000000076b4155c8 (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) at java.util.concurrent.ArrayBlockingQueue.poll(ArrayBlockingQueue.java:418) ... // 我们的业务调用栈 Thread-2 #18 prio5 os_prio31 tid0x00007fe0c5818800 nid0x6703 waiting for monitor entry [0x00007000093b6000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.Service.process(Service.java:42) - waiting to lock 0x000000076b1a8b80 (a com.example.Resource) at com.example.Controller.handleRequest(Controller.java:33) ... Thread-3 #19 prio5 os_prio31 tid0x00007fe0c5819000 nid0x6803 runnable [0x00007000094b9000] java.lang.Thread.State: RUNNABLE at com.example.Service.calculate(Service.java:55) - locked 0x000000076b1a8b80 (a com.example.Resource) ...如何解读线程1 (http-nio-8080-exec-1)状态是TIMED_WAITING原因是parking正在等待一个ConditionObject。这通常是使用了LinkedBlockingQueue、ThreadPoolExecutor等工作队列线程在等待任务到来。这是正常的I/O线程行为。线程2 (Thread-2)状态是BLOCKED原因是waiting for monitor entry明确指出了它在等待锁0x000000076b1a8b80。这是一个危险信号说明存在锁竞争。我们需要看是谁持有了这把锁。线程3 (Thread-3)状态是RUNNABLE并且它locked了线程2正在等待的同一个对象0x000000076b1a8b80死锁或严重锁竞争的嫌疑极大。我们需要进一步分析线程3的调用栈看它为什么长时间持有锁不释放是在进行复杂计算还是在等待其他资源。通过这样分析我们就能快速定位到性能瓶颈或死锁的根源——是某个同步块内的代码执行过慢还是锁的粒度设计得太粗。5. 高级话题状态转换中的陷阱与最佳实践理解了基本转换后我们再看几个容易踩坑的高级场景。5.1 interrupt() 中断机制对状态的影响Thread.interrupt()方法并不会直接强制停止一个线程它的作用是设置线程的中断标志位并尝试唤醒处于等待状态的线程。对RUNNABLE线程仅仅设置中断标志位线程需要自己通过Thread.interrupted()或isInterrupted()来检查并决定如何处理例如优雅地结束循环。对WAITING或TIMED_WAITING线程例如在sleep(),wait(),join(),park()中线程会被立即唤醒并抛出InterruptedException。同时其中断标志位会被清除在抛出异常前。这是实现线程可取消任务的关键机制。对BLOCKED线程调用interrupt()几乎没有任何即时效果因为它正在被动等待锁不响应中断。只有当它成功获取锁并从synchronized块中出来后你才能检查到中断标志位。最佳实践对于可能阻塞的任务一定要妥善处理InterruptedException。通常的做法是捕获异常在清理资源后要么直接返回要么再次设置中断标志位Thread.currentThread().interrupt()让上层代码感知而不是生吞掉异常。5.2 锁与状态的区别synchronized vs Lock这是另一个关键点。我们之前提到BLOCKED状态只对应synchronized。synchronized是JVM内置的监视器锁。竞争失败线程状态变为BLOCKED。ReentrantLock等显式锁当调用lock()方法获取锁失败时线程会进入WAITING或TIMED_WAITING状态因为其底层AQS使用了LockSupport.park()。在jstack中你会看到线程状态是WAITING (parking)而不是BLOCKED。这意味着使用jstack分析时如果看到大量WAITING (parking)且与锁相关很可能你的程序正在使用java.util.concurrent包下的显式锁。显式锁通常提供了更灵活的功能如可中断、可超时、公平锁等但也需要开发者更小心地管理锁的获取和释放必须在finally块中unlock()。5.3 线程状态与CPU资源消耗RUNNABLE状态的线程会参与CPU时间片的竞争消耗CPU资源。BLOCKED、WAITING、TIMED_WAITING状态的线程不消耗CPU除了极少数自旋锁实现的优化场景。它们被操作系统挂起等待特定事件。一个负载健康的服务器应用其线程大部分时间应处于WAITING/TIMED_WAITING等待网络I/O、任务队列或RUNNABLE合理计算状态。如果BLOCKED状态的线程比例异常高就是锁竞争激烈的明确信号。6. 常见问题排查与性能调优思路结合线程状态我们可以形成一套系统的并发问题排查方法。问题1应用响应慢CPU使用率却不高。排查思路用jstack抓取多次dump对比分析。很可能大量线程处于WAITING或TIMED_WAITING状态在等待数据库连接、外部API响应、或某个慢速资源。重点检查线程栈中等待的条件如ConditionObject、FutureTask.get()以及网络I/O调用。调优方向优化慢查询、增加连接池大小、为外部调用设置合理的超时、使用异步非阻塞IO如NIO。问题2CPU使用率持续100%但吞吐量很低。排查思路同样使用jstack。可能发现大量线程处于RUNNABLE状态并且调用栈停留在某个循环计算或密集的同步块内。也可能存在大量BLOCKED线程它们在“空转”地竞争锁虽然状态是BLOCKED不消耗CPU但那些持有锁的RUNNABLE线程可能在做无意义的忙等或死循环。调优方向优化算法复杂度、减小锁的粒度将一个大锁拆分成多个小锁、使用无锁数据结构如ConcurrentHashMap、检查是否有死循环。问题3应用运行一段时间后线程数暴涨内存溢出。排查思路检查是否有大量TERMINATED状态的线程对象未被回收这通常不是问题根源或者更关键的是检查是否有大量WAITING状态的线程永远无法被唤醒。例如生产者线程停止了但消费者线程还在take()一个空队列上无限等待。这属于线程泄漏。调优方向使用线程池管理线程生命周期、为等待操作设置超时、确保资源清理逻辑如关闭连接、取消任务能被正确执行。问题4如何确认死锁排查思路jstack工具本身具备死锁检测功能。在dump文件的最后通常会有一个专门的Found one Java-level deadlock:部分清晰地列出相互等待的线程和锁资源。即使没有这个部分通过手动分析BLOCKED线程的waiting to lock和locked信息也能找出循环等待链。掌握线程状态转换就等于为你的并发程序装上了最细致的监控探头。它不能直接解决所有问题但它能为你提供最准确、最直接的线索让你在复杂的多线程世界里不再盲目调试而是有的放矢。下次当你面对一个行为诡异的并发程序时别犹豫先抓一份线程转储从解读这些状态开始吧。