新闻详情

新闻详情

首页 / 资讯中心 / 详情

Java线程状态与JUC:从BLOCKED/WAITING到线程池死锁排查

发布时间:2026/9/26 6:45:42来源:尧图网络
Java线程状态与JUC:从BLOCKED/WAITING到线程池死锁排查
干我们这行第一次线上事故排查多半就是死在“线程状态”这一关上。我印象特别深的是实习期第一次处理线上告警半夜服务线程数暴涨jstack 一打满屏都是 WAITING 和 BLOCKED我只能对着日志干着急。后来把 Java 线程状态从 NEW 到 TERMINATED 完整啃了一遍才明白那天其实是“死锁 线程池队列堆积”双重问题叠在一起了。这套知识面试也是必考——让说说 Java 线程有哪些状态、怎么流转十个实习生九个能背出 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED但再追问一句“JUC 的 Lock 等锁会是 BLOCKED 状态吗”立刻卡壳。这篇就把 JUC 背景下的线程状态从定义到底层逻辑、从 API 操作到线上排障给你完整捋一遍适合刚开始学 JUC、正在准备 Java 面试以及想真正掌握并发排查思路的同学。1. 状态总览与最小认知先回答“线程状态到底是什么”1.1 Thread.State 枚举与 JVM 层面的抽象线程状态不是什么玄学它就是java.lang.Thread内部枚举State中定义的 6 个值public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }每个线程在任意时刻都处于这 6 个状态之一通过thread.getState()可以拿到当前瞬时状态。这里要注意这是JVM 层面抽象出来的状态并不是操作系统线程状态的直接映射。平台线程在 OS 里还有 ready、running、blocked 等细分但 JVM 只需要关心自己这一层的模型。状态通俗理解典型进入方式NEW刚 new 出来还没 startnew Thread()RUNNABLE正在执行或等待 CPU 调度包含 IO 等待start()之后、run()执行中BLOCKED等 synchronized 的 monitor 锁进入同步块/方法时锁被占用WAITING无期限等待需要被唤醒wait()、join()、LockSupport.park()TIMED_WAITING有期限等待超时自动恢复sleep()、wait(timeout)、join(timeout)、parkNanos()TERMINATED线程执行完毕或异常终止run()返回或抛出未捕获异常有一个核心认知必须建立状态是 JVM 内部分析线程行为用的不是给业务做并发协作用的。很多人写代码想用getState()判断另一个线程是否执行完这是典型错误姿势后面第五节我会单独说。1.2 一条最简单的线程生命周期从 new 到结束先看一个最小闭环public class LifecycleDemo { public static void main(String[] args) throws Exception { Thread t new Thread(() - System.out.println(执行中)); System.out.println(1. start 前: t.getState()); // NEW t.start(); Thread.sleep(50); System.out.println(2. start 后: t.getState()); // 大概率 RUNNABLE t.join(); System.out.println(3. join 后: t.getState()); // TERMINATED } }输出大概是1. start 前: NEW 2. start 后: RUNNABLE 3. join 后: TERMINATED这只是最简单的一条直线路径。真实的多线程场景里状态会在 RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 之间反复横跳。下面从每个状态单独拆解把容易混的边界全部说透。2. 六个状态逐一拆解最容易踩坑的边界2.1 NEW最容易被忽略的“创建未启动”new Thread()执行完线程对象已经创建但 JVM 还没有为该线程分配任何执行资源此时状态就是 NEW。它只是 Java 层的普通对象和new String()本质上没区别。NEW 状态最经典的考点是同一个线程不能 start 两次。Thread t new Thread(() - System.out.println(run)); t.start(); t.start(); // 抛 IllegalThreadStateException原因在底层start0()这个 native 方法JVM 内部一个线程只能被启动一次启动完成后内部状态位被标记再次 start 直接合法性校验失败。这个校验在 JDK 源码里对应的是threadStatus ! 0的判断一旦确认就抛出IllegalThreadStateException。如果你遇到“线程不能复用”的报错正确做法是重新创建一个Thread对象而不是想办法 reset 老线程。线程的生命周期是单向的。2.2 RUNNABLE就绪与运行合并后的真相很多初学者看到 RUNNABLE 就想当然以为是“正在运行”但准确说法是线程处于可运行状态要么正在 CPU 上执行要么在等待操作系统调度器给它分配时间片。JVM 把 OS 层面的 ready 和 running 合并成了一个 RUNNABLE原因是 Java 线程和 OS 线程绑定后JVM 无法精确感知调度器什么时候切走了 CPU索性只区分“有资格跑”和“没资格跑”。这也是为什么线程执行文件读取、网络 IO 时状态依然是 RUNNABLE。IO 引起的阻塞发生在 OS 内核层JVM 完全没有感知所以在 Java 线程状态里看不到单独的“IO 等待”。这点线上排查特别容易踩坑一个线程堆栈显示 RUNNABLE但对应日志里它在等数据库响应你第一反应可能是“它在空转占 CPU”其实 CPU 占用率如果是 0那只是 OS 把它挂起了Java 层面不知道而已。判断方式要看堆栈顶部和实际耗时不能单看状态名。Thread.yield()也是 RUNNABLE 的经典知识点调用 yield 只是让出当前 CPU 时间片线程立刻重新参与调度状态完全不变依然是 RUNNABLE。2.3 BLOCKED只有 synchronized 才会带来的状态BLOCKED 从定义上讲只有一种情况线程尝试进入 synchronized 同步块或同步方法但对应的 monitor 锁被其他线程持有于是进入阻塞。注意这句话的两个限定词一是“synchronized”二是“monitor 锁”。JUC 包里的ReentrantLock.lock()、CountDownLatch.await()这些等锁场景都不会出现 BLOCKED这是面试最尖锐的分水岭。原因很直接synchronized 依赖 JVM 的 monitor 机制拿不到锁就由 JVM 直接挂起线程表现为 BLOCKED而 JUC 锁底层通过 AQS LockSupport.park()挂起线程表现是 WAITING。举个例子public class BlockedDemo { private static final Object lock new Object(); public static void main(String[] args) throws Exception { Thread t1 new Thread(() - { synchronized (lock) { System.out.println(t1 拿到锁睡 3 秒); try { Thread.sleep(3000); } catch (InterruptedException e) {} } }); Thread t2 new Thread(() - { synchronized (lock) { System.out.println(t2 进入同步块); } }); t1.start(); Thread.sleep(200); // 确保 t1 先拿到锁 t2.start(); Thread.sleep(200); // 让 t2 去抢锁 System.out.println(t1 state: t1.getState()); // TIMED_WAITING System.out.println(t2 state: t2.getState()); // BLOCKED t1.join(); t2.join(); } }这里 t1 sleep 不释放锁所以 t2 只能在 BLOCKED 里干等。输出确认了 t2 的状态。Thread.sleep()不释放锁Object.wait()释放锁这两者差别一定要刻在脑子里。2.4 WAITING 与 TIMED_WAITING两种等待的实质区别WAITING 和 TIMED_WAITING 本质是同一类线程主动让出 CPU等待某个条件发生后再继续。区别只有一个——要不要等超过指定时间。先说 WAITING无期限等待常见入口有三个Object.wait()不传参数等待被notify/notifyAll唤醒Thread.join()等待另一个线程终止LockSupport.park()等待unpark唤醒TIMED_WAITING带超时等待常见入口也有三个Thread.sleep(millis)单纯让线程睡一会儿Object.wait(timeout)/Thread.join(timeout)等待一段时间后自动醒LockSupport.parkNanos(nanos)/LockSupport.parkUntil(deadline)AQS 内部大量使用这里有一个高频面试陷阱sleep()会释放锁吗答案是不会。sleep 只是让当前线程暂停执行但线程持有的锁一概不释放。如果某个线程 sleep 前占着 synchronized 锁其他线程只能继续 BLOCKED 等待。而wait()恰恰相反它在进入等待队列的同时释放 monitor 锁这样其他线程才有机会拿到锁。另一个容易混淆的是join()的底层实现。Thread.join()内部是一个while (isAlive()) { wait(0); }循环也就是说 join 过程中当前线程持有的是被 join 线程的“对象 monitor”等待状态是WAITING (on object monitor)。用 jstack 看的时候这个括号注释能帮你快速判断它是 wait 还是 park。2.5 TERMINATED线程终点与无法重启的事实当run()方法正常返回或者执行过程中抛出未捕获异常线程就进入 TERMINATED。正常返回很好理解关键是未捕获异常——Java 里线程内部抛出 RuntimeException 如果没人 catch线程会直接死在异常上状态变为 TERMINATED而不会影响其他线程。想在线程死亡前拿到异常可以设置UncaughtExceptionHandlerThread t new Thread(() - { throw new IllegalStateException(boom); }); t.setUncaughtExceptionHandler((thread, throwable) - System.out.println(捕获到异常: throwable.getMessage())); t.start();TERMINATED 后再调用start()同样会抛IllegalStateException因为线程生命周期的方向是单向的。想重新执行逻辑就重建对象。这个约束看起来简单但在线程池场景经常被误解——线程池里的 Worker 线程明明可以反复执行多个任务怎么不违反这个规则其实线程池复用的是“执行任务的能力”Worker线程的run()内部是一个循环不断从队列里拿任务执行所以 Worker 线程本身没有进入 TERMINATED它是在 RUNNABLE / WAITING 之间反复切换。3. 状态流转完整链路从 start 到终结的每一条转换规则3.1 start() 内部逻辑与 NEW 到 RUNNABLE 的唯一入口start()是整个生命周期里最重要的一个动作。看 JDK 源码start()大概做了三件事校验threadStatus 0否则抛IllegalThreadStateException把当前线程加入ThreadGroup方便后续统一管理调用 native 的start0()由 JVM 创建底层 OS 线程并执行run()所以 NEW 到 RUNNABLE 只有start()这一个入口。run()方法是不能直接调的直接调run()就是在当前线程里同步执行一段普通方法不会开启新线程这是实习生最容易犯的错。3.2 一张状态转换触发表动手对照我用文字整理一张触发转换表比画状态机图更清晰你可以贴在工位上看当前状态目标状态触发动作NEWRUNNABLE调用start()RUNNABLEBLOCKED进入被占用的 synchronized 代码块/方法BLOCKEDRUNNABLE抢到 monitor 锁线程重新参与调度RUNNABLEWAITING调用无参wait()/ 无参join()/LockSupport.park()RUNNABLETIMED_WAITING调用sleep()/ 带超时wait()/join()/parkNanos()WAITING / TIMED_WAITINGRUNNABLE被notify()/notifyAll()/unpark()唤醒或超时时间到RUNNABLETERMINATEDrun()正常返回或抛出未捕获异常WAITINGBLOCKED被唤醒后重新竞争 synchronized 锁失败最后一行值得多说一句如果两个线程都在wait()中被notifyAll()唤醒后同时去抢同一把锁只有一个能成功另一个会进入 BLOCKED。也就是说线程从 WAITING 被唤醒不保证直接回到 RUNNABLE路径可能是WAITING → BLOCKED → RUNNABLE这个细节经常被遗漏。3.3 用代码现场采样状态是瞬时快照写一个演示程序主线程不停打印子线程的状态你会看到状态从 NEW 一路走到 TERMINATEDpublic class StateSamplingDemo { public static void main(String[] args) throws Exception { Object lock new Object(); Thread t new Thread(() - { synchronized (lock) { try { System.out.println(线程进入等待); lock.wait(500); System.out.println(线程被超时唤醒); } catch (InterruptedException e) { e.printStackTrace(); } } // 这里再模拟一段耗时操作 for (int i 0; i 100000; i) { Math.pow(i, 2); } }); System.out.println(start 前: t.getState()); // NEW t.start(); for (int i 0; i 10; i) { Thread.sleep(100); System.out.println(采样 i : t.getState()); } t.join(); System.out.println(最终: t.getState()); // TERMINATED } }运行后你会看到状态在 RUNNABLE、TIMED_WAITING、TERMINATED 之间变化。这里先立个规矩getState()是快照。它只反映调用那一刻线程所处的状态下一纳秒可能就变了。所以在业务逻辑中不要依赖“先看状态再决定后续动作”这种模式并发协作要基于锁、条件变量、队列这些真正的同步原语而不是基于状态判断。4. JUC 实战视角锁、同步器与线程池里的线程状态4.1 synchronized 与 ReentrantLock为什么一个 BLOCKED 一个是 WAITING这是 JUC 面试里区分度极高的一个点。同样是在等锁synchronized 等锁的线程状态是BLOCKEDReentrantLock 等锁的线程状态是WAITING (parking)。原理要追溯到两条不同的实现路线。synchronized 走的是 JVM 内置 monitor 机制每个 Java 对象头里都带 monitor 信息拿不到锁的线程由 JVM 直接挂起进入阻塞集合这就是 BLOCKED 的来源。ReentrantLock 则完全建立在 AQSAbstractQueuedSynchronizer之上锁被占用时当前线程会被包装成一个 Node 节点挂进同步等待队列然后调用LockSupport.park()主动挂起。park()产生的状态就是 WAITING堆栈注释是WAITING (parking)。这也解释了为什么 ReentrantLock 支持tryLock(timeout)——AQS 在等待队列里用的是可中断、可带超时的parkNanos()等不到锁可以按时间自动醒来重新尝试。synchronized 做不到这种精细流程锁竞争失败后只能傻等没有“我不想等了”的选项直到 JDK 后面对 synchronized 做了锁升级优化也只是优化性能和唤醒路径语义上依然没有 tryLock 能力。如果你在排查问题时看到一堆WAITING (parking)堆栈提到AbstractQueuedSynchronizer$ConditionNode或LockSupport.park基本可以断定是 JUC 锁或同步器的等待队列。4.2 CountDownLatch、Semaphore 等同步工具的状态表现AQS 是 JUC 同步工具的地基所以它们的行为高度一致CountDownLatch.await()计数不为 0 时线程在 AQS 共享节点上 park状态WAITING (parking)Semaphore.acquire()拿不到许可时同样 park状态WAITING (parking)CyclicBarrier.await()内部通过Condition.await()等待表现也是 WAITING但堆栈注释偏向on object monitor风格不同 JDK 版本有差异只要带了 timeout 参数的版本状态就变成TIMED_WAITING实战里看 jstack 时如果一堆线程卡在CountDownLatch.await上且状态全是 WAITING别急着加线程——先看是谁把 count 卡住了。通常是有线程一直没调用countDown()可能是死锁也可能是业务异常把countDown()跳过。这时候堆栈只能告诉你“在哪等”还得配合业务日志找到“谁没完成”。4.3 线程池空闲与忙碌WAITING 与 RUNNABLE 的识别线程池是最常见的线程复用场所。它的 Worker 线程状态非常有规律适合作为状态学习的活教材。一个核心线程执行任务时状态是 RUNNABLE。但任务执行完队列里没有新任务时Worker 会执行getTask()内部调用workQueue.take()。take()会一直阻塞等待队列出现新元素于是线程挂起状态变成WAITING (parking)堆栈能看到at java.util.concurrent.locks.LockSupport.park(...) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(...) at java.util.concurrent.LinkedBlockingQueue.take(...) at java.util.concurrent.ThreadPoolExecutor.getTask(...)这就能解释很多“线程数很高但全睡觉”的现象线程池空闲时大量核心线程都是 WAITING这完全正常不是泄漏。如果allowCoreThreadTimeOuttrue空闲线程走的是workQueue.poll(timeout)状态就变成TIMED_WAITING超时后线程退出。需要警惕的反而是另一种情况队列里有大量堆积任务但 Worker 线程全部 WAITING。这说明任务不是“没人取”而是取到任务后执行到一半卡住了——可能是阻塞在某个锁或 IO 上。你得顺着堆栈往下看任务栈帧找到真正的瓶颈。4.4 死锁状态组合与 jstack 自带检测死锁是线程状态的经典综合体。经典场景线程 A 持有锁 L1 等待锁 L2线程 B 持有锁 L2 等待锁 L1两个线程同时卡在 synchronized 等待上状态全是 BLOCKEDThread-A ... BLOCKED (on object monitor), waiting for: 0x...L2 Thread-B ... BLOCKED (on object monitor), waiting for: 0x...L1jstack 还会在末尾直接输出一屏死锁检测信息Found one Java-level deadlock: Thread-A: waiting to lock Monitor...L2 which is held by Thread-B这个检测功能是 jdk 原生提供的黄金排查手段。除了 synchronized 死锁JUC 锁之间的循环等待比如两个 ReentrantLock 互相嵌套也会被检测出来不过堆栈注释会指向LockSupport.park那一层。训练排障能力最好的办法就是在本地故意写一个死锁 demodump 出来对照学习。反复看几次后线上遇到就能一眼认出来。5. 线上排障实录怎么用 jstack 和 Thread.getState() 定位线程问题5.1 jstack 中各种状态标志的含义对照线上最实用的工具还是jstack。它输出的线程信息里第二行就是java.lang.Thread.State括号里的注释是定位关键。我把常见组合整理成表jstack 输出真实含义RUNNABLE正在执行或在 OS 层等待调度/IOBLOCKED (on object monitor)正在等 synchronized 锁WAITING (parking)被LockSupport.park()挂起AQS 场景最多WAITING (on object monitor)正在Object.wait()或Thread.join()TIMED_WAITING (sleeping)正在Thread.sleep()不释放锁TIMED_WAITING (on object monitor)带超时的wait()/join()TIMED_WAITING (parking)带超时的parkNanos()/parkUntil()拿到这些信息后结合堆栈前 20 行就能判断线程卡在哪。如果状态是 WAITING 但堆栈显示jdbc.ConnectionManager.getConnection说明几乎肯定是从连接池拿连接时等不到空闲连接如果状态是 RUNNABLE 但堆栈卡在SocketInputStream.read那是网络 IO 等待多半是下游服务慢别瞎找 CPU 问题。5.2 一次线程池等待问题的排查全过程我说一个之前遇到的真实场景。一个服务高峰期线程数打满服务整体无响应。我先jstack pid dump.txt打了一份线程栈然后从两个维度看统计线程状态分布grep java.lang.Thread.State dump.txt | sort | uniq -c结果大概是WAITING (parking)有一百多个RUNNABLE有十几个BLOCKED有七八个第一反应是线程池空闲线程太多 若干锁竞争但看业务指标请求量明明很大。继续看堆栈发现大量WAITING (parking)的线程都卡在同一个地方等待连接池的Semaphore.acquire()。这说明不是线程池的问题而是连接池的连接数被耗尽。再往下看那十来个RUNNABLE线程堆栈停在数据库驱动读取响应的SocketInputStream.read。到这里问题闭环了数据库响应慢导致连接持有时间变长连接池被占满后续请求在Semaphore.acquire()排队线程池队列随后堆积整体假死。这轮排查中线程状态的作用就是第一层漏斗WAITING 数量暴涨指向连接池RUNNABLE 堆栈指向数据库慢查询。没有状态分布你只能在那猜。5.3 Thread.getState() 的正确使用姿势与局限getState()在写监控告警、健康检查时很有用比如定期统计线程状态分布发现 BLOCKED 持续增长时报警。但它有三个不能踩的坑第一它是瞬时快照。两个线程同时去 getState 同一个线程结果可能不同拿到状态之后马上又变了。凡是“根据状态决定要不要下一步操作”的代码都不要写。第二不能替代同步机制。比如主线程想等子线程执行完正确方式用join()、CountDownLatch、Future绝对不要循环while (t.getState() ! TERMINATED)去忙等。忙等浪费 CPU还会吃到内存可见性问题的亏。第三只代表 JVM 视角。RUNNABLE 不代表 CPU 在跑可能是 IO 等待WAITING 不代表工作没在进行可能是在排队。排查时要结合堆栈、耗时、资源指标一起看不能就着状态名断案。从开源监控工具比如 Arthas 的thread命令能看到更细的线程信息和耗时统计比手工 getState 直观得多。另外补充一句如果你已经在折腾虚拟线程这套状态的语义会有偏差。虚拟线程阻塞时平台线程可以被重新调度去跑其他虚拟线程所以它的状态看起来会更“虚”。但底层对于 JUC 锁、wait/notify 的抽象原理并没有变把平台线程这套吃透了再往下看虚拟线程更从容。写在最后我的几个实操习惯个人经验学线程状态这一块光背面试题没有用重点在“看图说话”。我建议你在本地装一个 Arthas随便找个多线程 Demo 跑起来用thread -n 3看 CPU 占用最高的线程堆栈再用thread --state BLOCKED看阻塞线程分布亲眼看几次状态变化比对着书看十遍有效得多。另外排查线程问题前一定先打线程 dump 存底。很多线上问题转瞬即逝等你想起来看的时候状态已经变了留一份 dump 至少能事后复盘。这个习惯我在几个大故障里都靠它保住了关键证据。把这两个习惯养成线程状态就不再只是面试八股而是你手里真正好用的排障工具。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Isaac Gym高扭矩腿式机器人强化学习环境配置与训练避坑指南 2026/9/26 7:24:31

Isaac Gym高扭矩腿式机器人强化学习环境配置与训练避坑指南

简介:基于Isaac Gym的HighTorque腿式机器人强化学习环境,面向机器人运动控制与深度强化学习研究者,解决高扭矩关节驱动腿式机器人在仿真环境中的训练与部署难题。资源包共包含63个文件,以Python脚本、STL三维模型和配置文件为核心…

阅读更多 →
小样本铁路轨道故障图像识别:从CNN到YOLOv5的分类实践 2026/9/26 7:24:31

小样本铁路轨道故障图像识别:从CNN到YOLOv5的分类实践

简介:面向铁路轨道视觉检测场景的已标注图像分类数据集,围绕轨道表面损坏与未损坏的二分类任务构建,适合正在开展缺陷识别、智慧运维或图像分类相关课题的算法工程师、科研人员与学生使用。数据包含约380张轨道图像,标签分为损坏、…

阅读更多 →
从手动到自动:Harness如何让70%的Claude Code任务无需打开 2026/9/26 7:24:31

从手动到自动:Harness如何让70%的Claude Code任务无需打开

1. 从“打开 Claude Code”到“根本不用打开”:一个团队工作流的真实演变一年前,我们团队几乎所有人的日常都长一个样:早上到工位第一件事,打开终端,敲claude回车,然后开始跟它对话。写代码、改 bug、查文档…

阅读更多 →
多Agent协作架构实战:从单兵作战到团队协同的工程化落地 2026/9/26 7:24:31

多Agent协作架构实战:从单兵作战到团队协同的工程化落地

1. 从单兵作战到团队协同:多Agent架构到底在解决什么问题单Agent系统在过去两年里几乎成了所有AI应用的默认形态——一个模型、一段提示词、一套工具,用户输入问题,模型给出回答。这种模式在简单问答、文本生成、代码补全等场景下表现不错&am…

阅读更多 →
Isaac Gym高扭矩腿式机器人强化学习环境搭建与训练调参实战 2026/9/26 7:24:31

Isaac Gym高扭矩腿式机器人强化学习环境搭建与训练调参实战

简介:面向腿式机器人强化学习研究者与机器人控制开发者的完整环境资源包,基于英伟达Isaac Gym仿真平台,聚焦HighTorque高扭矩腿式机器人控制策略训练与仿真验证,适合已有一定强化学习基础、希望围绕机器人模型开展策略复现和二次开…

阅读更多 →
VMware安装卡在虚拟网络驱动?彻底解决与排查指南 2026/9/26 7:24:11

VMware安装卡在虚拟网络驱动?彻底解决与排查指南

1. 卡在“正在安装虚拟网络驱动程序”到底卡在了哪装 VMware Workstation 这件事,说简单也简单,一路下一步就完事;说坑也真坑,很多人第一次装就栽在同一个地方——进度条走到“正在安装虚拟网络驱动程序”这一步,然后就…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉