避免活性故障:死锁、活锁、饥饿 + 死锁排查【重点精讲 + 总结】 活性Liveness程序最终一定能向前推进、完成任务与之对应的是安全性Safety坏事情永远不会发生。 死锁、活锁、饥饿全部属于活性故障线程卡在那里一直无法正常执行完业务。一、三个概念对比核心区分1. 死锁Deadlock【最常见】定义一组线程互相持有对方需要的锁同时等待对方释放锁所有人都静止不再做任何动作永久卡住。一句话大家拿着锁、互相等全都不动了死锁 4 个必要条件四个同时满足才会死锁破坏任意一个即可避免互斥条件资源是排他锁同一时间只能一个线程持有synchronized、ReentrantLock 排他模式持有并等待线程已经拿到 A 锁不释放再去请求 B 锁不可剥夺锁只能持有者主动释放别人抢不走、不能强行拿走循环等待线程形成环形等待链T1 等 T2、T2 等 T3 … Tn 等 T1✅ 典型场景 T1锁 A → 请求锁 B T2锁 B → 请求锁 A2. 活锁Livelock定义线程没有阻塞、没有挂起一直在运行、不断重试但是永远无法完成目标。线程状态是 RUNNABLE不是 BLOCKED/WAITING。一句话一直在忙活原地踏步没有进展✅ 典型场景 两个线程同时拿到资源检测冲突然后主动释放、退让再同时重试反复礼让谁都拿不到资源。 比如简易自旋锁冲突就 sleep 一小段两个线程休眠时间完全一样每次唤醒又撞车。区别死锁死锁线程不动活锁线程一直在跑。3. 饥饿Starvation定义线程一直拿不到它需要的资源长期得不到执行机会一直被插队迟迟无法推进甚至永远执行不到。一句话资源被优先级高 / 抢占能力强的线程持续霸占弱者一直轮不到✅ 典型场景ReentrantLock 默认非公平锁高并发下新来的线程直接抢到锁队列里等待很久的线程持续拿不到饥饿线程优先级设置差距过大低优先级线程几乎不被调度一个线程持有锁时间极长其他线程排队一直等区分三兄弟速记死锁都不动阻塞等待活锁一直在动但白跑自旋重试饥饿想跑但是轮不到它资源被抢占二、如何预防这三类活性故障✅ 死锁预防破坏 4 条件之一破坏持有并等待一次性申请所有需要的锁要么全部拿到一个都拿不到就不持有任何锁破坏不可剥夺支持锁超时、尝试获取tryLock(timeout)拿不到就释放已经持有的锁回头重试破坏循环等待【工程最常用】全局统一锁的获取顺序比如不管哪个线程要锁 A、B永远先拿 A再拿 B禁止反过来互斥条件很多业务无法破坏必须排他一般不在这里下手编码规范优先统一加锁顺序 使用带超时的 tryLock不要无脑用 synchronized 嵌套。✅ 活锁预防退让重试时加入随机退避时间jitter不要固定休眠时长限制重试次数超过次数直接失败不要无限自旋排队机制队列锁公平锁避免互相礼让撞车✅ 饥饿预防锁选择需要避免饥饿就用ReentrantLock (true) 公平锁FIFO 排队先来先得代价公平锁上下文切换多吞吐更低非公平性能高但有饥饿风险不要滥用线程优先级持有锁的代码尽量短锁内不做 IO、慢逻辑尽快释放锁限制单个线程占用资源时长三、重点死锁排查Java 线上排查流程实操前提现象服务卡住、接口超时、线程池不干活、CPU 不一定高死锁线程大多 BLOCKEDCPU 低这点和活锁区分排查步骤线上标准流程Step1确认进程 PIDjps -l # 或者 ps -ef | grep javaStep2导出线程栈核心命令两种方式任选推荐先 jstack# 直接打印线程栈到控制台 jstack pid # 输出到文件方便下载分析 jstack pid thread.txt重点jstack 会自动检测 JVM 死锁在文件末尾搜索关键字Found one Java-level deadlockJVM 可以直接发现synchronized / ReentrantLock 形成的死锁AQS 的死锁 jstack 能识别Step3看懂死锁日志模板Found one Java-level deadlock: Thread-1: waiting to lock monitor 0x00007f8b94003c68 (object 0x000000076ab34290, a java.lang.Object), which is held by Thread-0 Thread-0: waiting to lock monitor 0x00007f8b94003b28 (object 0x000000076ab34280, a java.lang.Object), which is held by Thread-1日志直接告诉你哪些线程各自在等哪个锁锁被哪个线程持有 往下看栈帧可以定位到代码行号直接找到嵌套加锁的地方。Step4辅助工具复杂场景JConsole / JVisualVM图形界面线程页检测死锁按钮一键查看Arthas阿里线上神器生产首选不用装 JDK 工具包# arthas 命令 thread -b # -b 专门检测阻塞死锁线程直接打印死锁信息比jstack更友好 thread -n 5 # 查看top5 CPU线程区分死锁和CPU高的活锁场景⚠️ 注意thread -b只能发现锁等待型死锁活锁、饥饿arthas 和 jstack 不会标记为 deadlock需要看线程状态 RUNNABLE、循环重试代码。Step5区分容易混淆现象死锁线程状态 BLOCKED / WAITINGjstack 标记 Found deadlockCPU 低活锁线程 RUNNABLE循环自旋重试CPU 可能偏高JVM 不报死锁饥饿部分线程一直在队列等待锁被其他线程持续拿到没有环形等待链JVM 不报死锁Step6修复方案拿到代码位置后统一加锁顺序优先方案改成 tryLock 超时超时释放锁抛出告警不永久阻塞拆锁把大锁拆细减少嵌套锁尽量不要嵌套获取多把锁替换锁结构用无锁 CAS、并发容器避开锁嵌套四、坑点提醒synchronized是不可中断、没有超时的一旦嵌套顺序错死锁后只能重启进程ReentrantLock 支持 tryLock (时间)、lockInterruptibly可控性远高于 synchronized适合多锁场景数据库死锁 ≠ Java 线程死锁数据库死锁是 InnoDB 事务锁要看 mysqlshow engine innodb statusjstack 看不到分布式死锁Redis 分布式锁跨进程、跨服务JVM 栈完全看不到要单独排查锁 key、释放逻辑、锁超时、续约五、终极浓缩总结背诵版✅ 活性三故障一句话死锁循环等待互相拿锁全部阻塞不动4 条件同时满足jstack 可直接检出。活锁无阻塞一直自旋重试、互相避让永远无法成功加随机退避、限制重试次数解决。饥饿资源持续被抢占部分线程长期拿不到资源公平锁、缩短锁持有时间缓解。✅ 死锁排查口诀jps 拿 pid → jstack/arthas thread -b → 搜 Found deadlock → 看线程栈定位代码行 优先统一锁顺序其次 tryLock 带超时尽量减少锁嵌套✅ 避坑总原则能不加嵌套锁就不加多锁必须定全局顺序优先带超时可中断锁锁内逻辑要短区分 JVM 线程死锁、DB 死锁、分布式锁死锁。