Java线程死锁是多线程争抢多个共享资源时因锁顺序不一致等导致的永久互相等待现象,必须同时满足互斥、持有并等待、不可剥夺、循环等待四条件;破坏任一条件(如统一锁序、tryLock超时、缩小同步范围)即可预防。

Java线程创建阶段本身不会直接引发死锁,但一旦开始使用同步机制(如 synchronized 或 ReentrantLock),尤其是多线程争抢多个共享资源时,死锁风险就立刻出现。关键不在于“怎么创建线程”,而在于“线程创建后如何加锁”。下面从典型场景、快速排查、实用预防三方面讲清楚怎么做。
常见死锁场景:从线程创建就开始埋雷
很多死锁其实发生在启动线程的瞬间——不是代码写得复杂,而是锁顺序没对齐。比如两个线程分别执行转账逻辑:
- 线程1:先锁账户A,再锁账户B;
- 线程2:先锁账户B,再锁账户A。
只要两个线程几乎同时启动,且各自拿到第一个锁后稍作延迟(比如 Thread.sleep(10)),就极大概率卡死。这种模式在订单扣减+库存更新、用户积分+优惠券核销等业务中非常普遍。
另一个易忽略点:静态锁对象被多个线程共用,但锁粒度设计不合理。例如用 new Object() 做锁,却误以为每个实例是独立的;或者把锁定义成 static final,导致本该隔离的操作被全局串行化,反而放大竞争和死锁可能。
立即学习“Java免费学习笔记(深入)”;
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
jstack 快速定位死锁(生产环境首选)
线上服务一旦卡住,第一反应不是重启,而是抓线程快照。三步到位:
- 执行
jps -l找到目标 Java 进程 PID(如12345); - 执行
jstack 12345 > thread_dump.txt导出完整线程栈; - 打开文件搜索 “Found one Java-level deadlock” —— JDK 会自动高亮死锁线程、持有锁、等待锁的全部信息,连锁对象的内存地址都列得清清楚楚。
不需要看几百行堆栈,只要看到这段标记,就能确认是否死锁、哪几个线程卷入、锁的循环路径是什么。这是最轻量、最可靠、无需额外依赖的排查方式。
从源头预防:破坏死锁四条件中的任意一个
死锁必须同时满足互斥、持有并等待、不可剥夺、循环等待四个条件。其中“互斥”是并发安全的前提,不能动;真正可操作的是后三个:
-
统一锁顺序:所有涉及多个资源的操作,强制按 ID、名称或哈希值排序后加锁。例如转账前比较两个账户对象的
System.identityHashCode(),小的先锁; -
用 tryLock + 超时:替换
synchronized,改用lock.tryLock(3, TimeUnit.SECONDS)。获取失败就释放已持锁并重试或降级,打破“持有并等待”; - 减少锁范围与嵌套:避免在 synchronized 块里调用外部方法(尤其可能触发另一把锁的方法),把计算、校验等非临界操作移出锁外;
- 避免锁对象泄露:不要把锁暴露给不可信调用方,也不要用字符串字面量或 getClass() 作为锁(容易被外部复用)。
这些不是理论建议,而是电商大促、支付对账等高压场景下反复验证过的硬性规范。

















