如何解决死锁问题?
                           
天天向上
发布: 2025-07-28 21:27:57

原创
311 人浏览过

死锁(Deadlock) 是并发编程中常见的问题之一,发生在多个线程彼此持有对方需要的资源,且都在等待对方释放资源时,导致系统永久阻塞。


一、死锁的四个必要条件

根据“Coffman 条件”,死锁的发生必须同时满足以下四个条件:

  1. 互斥条件:资源一次只能被一个线程占用。
  2. 占有且等待:线程持有部分资源,同时等待其它资源。
  3. 不可抢占:线程已获得资源不能被强制释放。
  4. 循环等待:多个线程之间形成一种“环形”资源等待关系。

打破任一条件,都可以避免死锁。


二、死锁的经典示例(Java)

public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread-1 locked lock1");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lock2) {
                    System.out.println("Thread-1 locked lock2");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread-2 locked lock2");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lock1) {
                    System.out.println("Thread-2 locked lock1");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

结果:两个线程都在等待对方释放资源,产生死锁。


三、解决死锁的方法

方法 1:破坏循环等待 —— 固定锁顺序

始终以固定顺序获取锁,防止互相等待。

// 使用 hashCode 比较决定加锁顺序
Object lockA = new Object();
Object lockB = new Object();

void doSafeLocking(Object o1, Object o2) {
    Object first = o1.hashCode() < o2.hashCode() ? o1 : o2;
    Object second = o1.hashCode() < o2.hashCode() ? o2 : o1;

    synchronized (first) {
        synchronized (second) {
            // 业务逻辑
        }
    }
}

方法 2:尝试锁(tryLock) + 超时 + 回退机制

使用 ReentrantLock.tryLock() 避免永久等待。

ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

boolean acquired = false;

try {
    if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
        if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
            acquired = true;
            // 执行业务
        }
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    if (acquired) {
        lock2.unlock();
        lock1.unlock();
    }
}

防止永久阻塞,适合高并发系统。


方法 3:避免资源占用并等待

将所有需要的资源一次性获取(加锁),避免先占一部分再等另一部分。

synchronized (lock1) {
    synchronized (lock2) {
        // 获取全部资源后再执行
    }
}

虽然代码简单,但需要任务可一次拿到所有资源才适用。


方法 4:使用线程调度或锁管理框架(高级解决方案)

  • 使用如 Akka、Disruptor、Actor 模型等避免共享资源的框架。
  • 数据库中使用 悲观锁 + 乐观锁 + 行级锁 + 事务超时 控制资源竞争。
  • 使用工具如 Java Flight Recorder、JConsole、Arthas 来检测和诊断死锁。

四、如何检测死锁?

Java 自带命令检测(JDK 工具)

jps               # 查看 Java 进程 ID
jstack <pid>      # 查看线程堆栈

在输出中若发现:

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x000000001c0d0e78 (object lock2)
...
"Thread-2":
  waiting to lock monitor 0x000000001c0d0e78 (object lock1)
...

即为死锁。


推荐资料

  • 《Java 并发编程实战》第 10 章:死锁与活锁
  • 官方文档:ReentrantLock
  • 工具推荐:
  • jstack:线程栈分析
  • Arthas:运行时排查工具
  • VisualVM:GUI 可视化工具
  • Deadlock Detector:IDE 插件

总结

死锁解决思路说明
固定加锁顺序打破“循环等待”条件
tryLock + 超时机制打破“不可抢占”条件
一次性获取所有资源打破“占有且等待”条件
减少锁粒度,使用无锁结构彻底规避共享资源竞争

更多详细内容请关注其他相关文章!

发表回复 0

Your email address will not be published. Required fields are marked *