并发安全问题(死锁)

一、死锁

1. 死锁定义

多个线程互相持有对方需要的锁,又都不释放自己的锁,

互相无限等待,程序彻底卡死、无法继续执行。

2. 死锁四个必要条件(缺一不可)

  1. 互斥条件

    资源是独占的,同一时刻只能被一个线程持有。

  2. 请求与保持

    线程已经持有一把锁,不释放旧锁,又去申请新锁。

  3. 不可剥夺

    锁只能自己主动释放,不能被其他线程强行抢走

  4. 循环等待

    线程之间形成环路锁依赖:T1 等 T2 的锁,T2 等 T1 的锁。

破坏任意一个条件,就能杜绝死锁。

3. 死锁避免 (开发实操方案)

  1. 统一锁的获取顺序(最常用、最简单)

    所有线程固定顺序申请锁,不会形成循环等待。

  2. 主动释放不用的锁

    不持有无用锁,破坏「请求与保持」。

  3. 使用带超时的锁

    tryLock(time),拿不到锁直接放弃、回退业务,不死等。

  4. 放弃嵌套锁

    尽量减少多层锁嵌套,从根源减少环路依赖。

  5. 使用 juc 工具替代手动加锁

    用 CountDownLatch、Semaphore、并发容器,减少手写 synchronized 嵌套。

4. 死锁检测

  1. jstack 命令

    导出线程快照,搜索 Found one Java-level deadlock,直接定位死锁线程、锁对象、代码行。

  2. JConsole / JVisualVM

    图形化工具,一键检测死锁线程。

  3. 线上监控

    线程池队列积压、接口超时、CPU 低负载、线程 BLOCKED 大量堆积,大概率死锁。


二、活锁(Live Lock)

1. 定义

线程没有阻塞、没有卡死,一直在运行、不断重试,

但是永远无法完成任务,一直在空转、互相谦让。

2. 产生场景

两个线程发现对方占用资源,主动退让、重试,

你让我、我让你,无限循环,谁都执行不完。

3. 解决

  • 加入随机等待时间,避免步调完全一致;
  • 固定优先级,避免无意义重试退让。

三、饥饿(Starvation)

1. 定义

某个线程一直拿不到需要的资源,长期得不到执行,一直饿死。

线程不死、不阻塞,就是永远没机会跑。

2. 产生原因

  1. 非公平:高优先级 / 高频线程一直抢占锁;
  2. 同步锁一直被大线程霸占;
  3. 常量抢占资源,弱势线程永久排队。

3. 解决

  • 使用公平锁 new ReentrantLock(true)
  • 限制锁持有时长,拆分大同步块;
  • 资源合理分配,避免线程长期霸占。

四、优先级反转(Priority Inversion)

1. 定义

高优先级线程,反而被低优先级线程阻塞,导致响应延迟、故障。

2. 产生流程

  1. 低优先级线程 持有 共享锁;

  2. 高优先级线程 也要这把锁,被迫阻塞等待;

  3. 中间优先级线程 不断抢占 CPU,一直调度运行;

  4. 结果:

    低优先级线程得不到 CPU、迟迟不释放锁;

    高优先级线程一直被卡,优先级完全失效

3. 解决方案

  1. 锁优先级继承

    低优先级线程拿到锁后,临时提升为高优先级

    避免被中间线程抢占 CPU,快速执行完释放锁。

  2. 优先级天花板策略

    限制持有锁的线程最高优先级,统一调度。

Java / 操作系统底层都会做优先级继承优化,缓解该问题。

极简原理总结

  1. 死锁:互斥 + 持有并请求 + 不可剥夺 + 循环等待;破坏任意一个即可预防。

  2. 死锁避免:顺序加锁、tryLock 超时、减少嵌套锁;

    死锁检测:jstack、可视化工具。

  3. 活锁:线程一直运行、互相退让,完不成任务;加随机延时解决。

  4. 饥饿:线程长期抢不到资源,一直等待;用公平锁解决。

  5. 优先级反转:高优先级等低优先级锁,被中间级线程插队拖慢;靠优先级继承解决。


并发安全问题(死锁)
http://hanqichuan.com/2026/04/16/java并发/并发安全问题(死锁)/
作者
韩启川
发布于
2026年4月16日
许可协议