Java死锁案例排查与规避
死锁示例代码
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1:先持有lock1,再获取lock2
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1 持有 lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("线程1 等待 lock2");
synchronized (lock2) {
System.out.println("线程1 获取 lock2");
}
}
});
// 线程2:先持有lock2,再获取lock1
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2 持有 lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("线程2 等待 lock1");
synchronized (lock1) {
System.out.println("线程2 获取 lock1");
}
}
});
t1.start();
t2.start();
}
}
死锁排查方法
使用jstack命令
# 1. 先找到Java进程ID jps -l # 2. 使用jstack分析线程堆栈 jstack -l <pid>
输出中会明确显示:

Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000001b3e4b28 (object 0x000000076b3e4b28, a java.lang.String),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000001b3e4b28 (object 0x000000076b3e4b28, a java.lang.String),
which is held by "Thread-1"
使用JConsole图形化工具
# 启动JConsole jconsole
步骤:
- 选择目标Java进程
- 点击"线程"标签
- 点击"检测死锁"按钮
- 查看死锁的线程及持有的锁
使用VisualVM
# 启动VisualVM jvisualvm
步骤:
- 选择目标进程
- 进入"线程"选项卡
- 查看线程状态,死锁线程会显示为红色
代码层面检测
public class DeadlockDetector {
public static void detectDeadlock() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadBean.findDeadlockedThreads();
if (threadIds != null) {
System.out.println("检测到死锁!");
ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadIds);
for (ThreadInfo info : threadInfos) {
System.out.println("死锁线程: " + info.getThreadName());
System.out.println("等待的锁: " + info.getLockName());
System.out.println("持有的锁: " + Arrays.toString(info.getLockedMonitors()));
}
}
}
}
死锁规避策略
保持锁的顺序一致
public class SafeDeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
// 使用统一的锁获取顺序
public void safeMethod1() {
synchronized (lock1) {
synchronized (lock2) {
// 业务逻辑
}
}
}
public void safeMethod2() {
synchronized (lock1) { // 保持相同顺序
synchronized (lock2) {
// 业务逻辑
}
}
}
}
使用锁超时机制
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class TimeoutLockDemo {
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void safeMethod() {
try {
// 尝试获取lock1,超时时间1秒
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
// 尝试获取lock2,超时时间1秒
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 业务逻辑
System.out.println("获取所有锁成功");
} finally {
lock2.unlock();
}
} else {
System.out.println("获取lock2超时,释放lock1");
}
} finally {
lock1.unlock();
}
} else {
System.out.println("获取lock1超时");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
使用Java并发工具类
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;
public class ConcurrentToolDemo {
private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
private final StampedLock stampedLock = new StampedLock();
public void safeMethod() {
// 使用StampedLock的乐观读
long stamp = stampedLock.tryOptimisticRead();
try {
// 业务逻辑
} finally {
if (!stampedLock.validate(stamp)) {
// 转换为悲观读锁
stamp = stampedLock.readLock();
try {
// 重新读取
} finally {
stampedLock.unlockRead(stamp);
}
}
}
}
}
使用数据库行级锁
// 使用数据库乐观锁避免死锁
public class DatabaseLockDemo {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updateWithOptimisticLock(int id, int version) {
String sql = "UPDATE user SET name = ?, version = version + 1 " +
"WHERE id = ? AND version = ?";
int result = jdbcTemplate.update(sql, "newName", id, version);
if (result == 0) {
throw new OptimisticLockException("数据已被其他线程修改");
}
}
}
最佳实践建议
- 锁粒度控制:尽量使用细粒度锁,减少锁的持有范围
- 锁超时:使用
tryLock()方法设置超时时间 - 统一锁顺序:所有线程按相同顺序获取锁
- 减少锁的嵌套:尽量避免在持有锁的情况下获取其他锁
- 使用原子操作:如
AtomicInteger、ConcurrentHashMap等 - 定期检测:使用监控工具定期检查死锁情况
- 代码审查:特别是涉及多线程锁的代码
死锁检测工具对比
| 工具 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| jstack | 命令行,快速 | 需要知道PID | 线上紧急排查 |
| JConsole | 图形化,直观 | 需要GUI环境 | 开发/测试环境 |
| VisualVM | 功能丰富 | 启动较慢 | 性能调优 |
| Java代码检测 | 可自动化 | 需要额外编码 | 监控系统集成 |
通过以上方法,可以有效排查和规避Java死锁问题。