本文目录导读:
我来给你整理一份详细的死锁排查实战案例,从现象到解决手把手拆解。
死锁场景复现
典型业务场景
一个转账系统中,同时进行A→B和B→A的转账操作,就容易触发死锁。
public class TransferService {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
// 转账方法:from向to转账
public static void transfer(String from, String to, int amount) {
// 获取第一个账户的锁
synchronized (getLock(from)) {
System.out.println(Thread.currentThread().getName()
+ " 获取到 " + from + " 的锁");
// 模拟业务处理,增加死锁概率
try { Thread.sleep(100); } catch (InterruptedException e) {}
// 获取第二个账户的锁
synchronized (getLock(to)) {
System.out.println(Thread.currentThread().getName()
+ " 获取到 " + to + " 的锁");
// 执行业务逻辑
System.out.println(from + " 向 " + to + " 转账 " + amount);
}
}
}
private static Object getLock(String account) {
return "A".equals(account) ? lockA : lockB;
}
public static void main(String[] args) {
// 线程1:A→B转账
new Thread(() -> transfer("A", "B", 100), "转账线程-1").start();
// 线程2:B→A转账
new Thread(() -> transfer("B", "A", 200), "转账线程-2").start();
}
}
死锁发生过程分析
执行流程时序
线程-1 线程-2
| |
|--获取lockA(成功) |--获取lockB(成功)
|--sleep(100ms) |--sleep(100ms)
| |
|--尝试获取lockB(等待...) |--尝试获取lockA(等待...)
| ↑ | ↑
| └── 死锁!互相等待对方释放资源
死锁产生的四个必要条件
| 条件 | 状态 | 说明 |
|---|---|---|
| 互斥条件 | ✅ 满足 | 锁只能被一个线程持有 |
| 持有并等待 | ✅ 满足 | 持有自己的锁,还等待对方的锁 |
| 不可剥夺 | ✅ 满足 | 锁只能由持有者自己释放 |
| 循环等待 | ✅ 满足 | 线程1等线程2的锁,线程2等线程1的锁 |
死锁排查实战步骤
Step 1:找到问题进程
# 方法1:使用jps查找Java进程 $ jps -l 12345 com.example.TransferService # 方法2:使用ps查找 $ ps -ef | grep java
Step 2:使用jstack分析线程栈
# 导出线程栈信息 $ jstack 12345 > thread_dump.txt # 或者直接查看 $ jstack 12345
关键输出分析:
Found one Java-level deadlock:
=============================
"转账线程-2":
waiting to lock monitor 0x0000000012345678 (object 0x00000000abcdef01,
a java.lang.Object),
which is held by "转账线程-1"
"转账线程-1":
waiting to lock monitor 0x0000000012345679 (object 0x00000000abcdef02,
a java.lang.Object),
which is held by "转账线程-2"
Java stack information for the threads listed above:
===================================================
"转账线程-2":
at com.example.TransferService.transfer(TransferService.java:24)
- waiting to lock <0x00000000abcdef01> (a java.lang.Object)
at com.example.TransferService.lambda$main$1(TransferService.java:43)
at com.example.TransferService$$Lambda$1/0x00000008.run(...)
"转账线程-1":
at com.example.TransferService.transfer(TransferService.java:24)
- waiting to lock <0x00000000abcdef02> (a java.lang.Object)
- locked <0x00000000abcdef01> (a java.lang.Object)
at com.example.TransferService.lambda$main$0(TransferService.java:39)
at com.example.TransferService$$Lambda$0/0x00000008.run(...)
Step 3:使用jconsole图形化工具排查
# 启动jconsole $ jconsole 12345
操作路径: 线程 → 检测死锁
显示效果:
线程 "转账线程-1" 正在等待 "转账线程-2" 持有的锁
线程 "转账线程-2" 正在等待 "转账线程-1" 持有的锁
Step 4:使用VisualVM深入分析
# 启动VisualVM $ visualvm
排查步骤:
- 在左侧选择目标进程
- 点击"线程"标签页
- 点击"线程Dump"按钮
线程Dump结果:
线程名称: 转账线程-1
状态: BLOCKED on ObjectMonitor
等待获取: 0x00000000abcdef02
已持有: 0x00000000abcdef01
堆栈:
- TransferService.transfer(TransferService.java:24)
- TransferService$$Lambda$0.run(...)
- java.lang.Thread.run(Thread.java:748)
线程名称: 转账线程-2
状态: BLOCKED on ObjectMonitor
等待获取: 0x00000000abcdef01
已持有: 0x00000000abcdef02
堆栈:
- TransferService.transfer(TransferService.java:24)
- TransferService$$Lambda$1.run(...)
- java.lang.Thread.run(Thread.java:748)
解决方案
方案1:固定锁顺序(推荐)
public static void transfer(String from, String to, int amount) {
// 根据账户名排序,保证锁获取顺序一致
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
Object firstLock = fromHash < toHash ? getLock(from) : getLock(to);
Object secondLock = fromHash < toHash ? getLock(to) : getLock(from);
synchronized (firstLock) {
synchronized (secondLock) {
// 业务逻辑
}
}
}
方案2:使用显式锁超时
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class TransferService {
private static final ReentrantLock lockA = new ReentrantLock();
private static final ReentrantLock lockB = new ReentrantLock();
public static boolean transfer(String from, String to, int amount) {
ReentrantLock firstLock = getLock(from);
ReentrantLock secondLock = getLock(to);
try {
// 尝试获取第一个锁,最多等待5秒
if (!firstLock.tryLock(5, TimeUnit.SECONDS)) {
return false;
}
try {
// 尝试获取第二个锁,同样设置超时
if (!secondLock.tryLock(5, TimeUnit.SECONDS)) {
return false; // 获取失败,上层决定是否重试
}
try {
// 业务逻辑
return true;
} finally {
secondLock.unlock();
}
} finally {
firstLock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}
方案3:数据库层面的解决方案
对于数据库操作,可以使用数据库的锁机制和事务隔离级别来避免死锁。
排查要点总结
| 排查工具 | 使用场景 | 关键信息 |
|---|---|---|
| jps | 查看进程 | 进程ID |
| jstack | 获取线程快照 | 锁的持有和等待关系 |
| jconsole | 实时监控 | 图形化死锁检测 |
| VisualVM | 深入分析 | 线程状态、锁详情 |
| jcmd | 轻量级命令 | Thread.print指令 |
| Arthas | 在线调试 | thread -n命令查看阻塞线程 |
排查口诀
一找进程 二抓快照 三看锁关系 四定位代码 五修复验证
死锁排查的核心是定位"谁持有谁的锁"以及"谁在等待谁",jstack是排查死锁最直接有效的工具。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播