线程死锁定位与避免

死锁(Deadlock)是指两个或多个线程互相持有对方需要的资源,导致所有线程永久阻塞。本文介绍如何快速定位、恢复和预防死锁。

死锁产生的四个必要条件

条件说明
互斥资源一次只能被一个线程占有
持有并等待线程持有至少一个资源,同时等待其他线程占有的资源
不可剥夺资源不能被强制从线程中拿走
循环等待存在一组线程,互相循环等待对方占有的资源

快速定位死锁

方法一:jstack 定位(推荐)

# 1. 找到 Java 进程 PID
jps -l
# 或
ps -ef | grep java
 
# 2. 打印线程堆栈
jstack -l <pid> > dump.log
 
# 3. 在 dump 文件中搜索死锁关键字
grep -A 30 "Found one Java-level deadlock" dump.log

jstack 输出示例

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00007f8c... (a java.lang.Object)
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00007f8c... (a java.lang.Object)
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at DeadlockDemo.methodB(DeadlockDemo.java:30)
    - waiting to lock <0x000000076b5f6d20> (a java.lang.Object)
    - locked <0x000000076b5f6d10> (a java.lang.Object)
"Thread-0":
    at DeadlockDemo.methodA(DeadlockDemo.java:20)
    - waiting to lock <0x000000076b5f6d10> (a java.lang.Object)
    - locked <0x000000076b5f6d20> (a java.lang.Object)

方法二:工具检测

  • jconsole:连接进程 → 线程 Tab → 检测死锁
  • VisualVM:线程 Tab → 死锁检测
  • JMC (JDK Mission Control):线程页签自动检测

方法三:代码层主动检测

// 通过 ThreadMXBean 代码检测
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
// findMonitorDeadlockedThreads() 也可以检测(但不够全面)

线上恢复策略

恢复方法操作风险说明
kill -3kill -3 <pid>打印线程 dump 到 stdout,不杀进程
重启服务重启应用最暴力但最有效的恢复方式
强制释放kill 死锁线程可能导致数据不一致(不推荐)
动态调参调整线程池/超时参数缓解而非解决

代码层面避免死锁

1. 固定锁顺序(最推荐)

// 错误:不同线程获取锁的顺序不一致
// 线程A: lock A → lock B
// 线程B: lock B → lock A  → 死锁!
 
// 正确:全局统一锁顺序
// 线程A: lock A → lock B
// 线程B: lock A → lock B  → 安全
// 按一定规则(如 hashCode、System.identityHashCode)排序

2. 使用显式锁的超时机制

ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
 
// 使用 tryLock 指定超时时间
if (lock1.tryLock(3, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(3, TimeUnit.SECONDS)) {
            try {
                // 业务逻辑
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}
// tryLock 超时后可以释放已获取的锁,避免死等

3. 减少锁粒度

  • ConcurrentHashMap 替代 HashMap + synchronized
  • 用读写分离替代排他锁
  • 使用分段锁 / Striped Lock

4. 使用并发工具类

场景推荐工具说明
等待一组任务完成CountDownLatch一次性计数
线程间同步屏障CyclicBarrier可重用
信号量控制Semaphore限制并发数
生产者-消费者BlockingQueue线程安全队列

参考链接