跳转到内容

死锁原理与预防

来自代码酷
Admin留言 | 贡献2025年5月12日 (一) 00:26的版本 (Page creation by admin bot)

(差异) ←上一版本 | 已核准修订 (差异) | 最后版本 (差异) | 下一版本→ (差异)

模板:Note

死锁原理与预防[编辑 | 编辑源代码]

定义与基本条件[编辑 | 编辑源代码]

死锁(Deadlock)指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象。若无外力干涉,这些线程将无法继续推进。死锁的发生必须同时满足以下四个必要条件(Coffman条件):

  1. 互斥条件:资源一次只能被一个线程占用。
  2. 占有并等待:线程持有至少一个资源,并等待获取其他被占用的资源。
  3. 非抢占条件:已分配给线程的资源不能被其他线程强行夺取。
  4. 循环等待条件:存在一个线程的循环等待链,每个线程都在等待下一个线程所占用的资源。

数学描述(使用集合论表示): Deadlock={T1,T2,,TnTiTi+1的等待关系形成环}

代码示例分析[编辑 | 编辑源代码]

以下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 threadA = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("ThreadA持有lock1");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                synchronized (lock2) {
                    System.out.println("ThreadA获取lock2");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("ThreadB持有lock2");
                synchronized (lock1) {
                    System.out.println("ThreadB获取lock1");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

输出结果可能为:

ThreadA持有lock1
ThreadB持有lock2
(程序无限挂起)

关键点说明:

  • 线程A持有`lock1`后尝试获取`lock2`
  • 线程B持有`lock2`后尝试获取`lock1`
  • 双方互相阻塞,形成循环等待

死锁检测与诊断[编辑 | 编辑源代码]

Java工具链[编辑 | 编辑源代码]

1. 使用`jstack`命令生成线程转储:

   jstack <pid> > thread_dump.txt

2. 查找输出中的`deadlock`关键词,典型报告如下:

   Found one Java-level deadlock:
   "ThreadB":
     waiting to lock monitor 0x00007f88a8003e58 (object 0x000000076ab270c8)
     which is held by "ThreadA"
   "ThreadA":
     waiting to lock monitor 0x00007f88a8003f98 (object 0x000000076ab270d8)
     which is held by "ThreadB"

可视化分析[编辑 | 编辑源代码]

graph LR A[ThreadA] -->|持有 lock1\n请求 lock2| B[ThreadB] B -->|持有 lock2\n请求 lock1| A

预防策略[编辑 | 编辑源代码]

策略 实现方法 示例
破坏互斥条件 使用共享资源(如读写锁) `ReentrantReadWriteLock`
破坏占有并等待 一次性申请所有资源
// 使用原子操作获取所有锁
while(!tryLockAll(lock1, lock2)) {
    releaseLocks();
    backoff();
}
破坏非抢占条件 设置超时机制
lock.tryLock(500, TimeUnit.MILLISECONDS)
破坏循环等待 规定资源请求顺序
// 总是先申请lock1再申请lock2

实际案例:数据库事务死锁[编辑 | 编辑源代码]

场景描述:

  • 事务T1先更新账户A再更新账户B
  • 事务T2先更新账户B再更新账户A

解决方案:

-- 规定所有事务必须按字母顺序操作账户
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT;

高级话题:哲学家就餐问题[编辑 | 编辑源代码]

经典同步问题演示死锁:

graph TB P1(哲学家1) -->|请求| F1(筷子1) P1 -->|请求| F2(筷子2) P2(哲学家2) -->|请求| F2 P2 -->|请求| F3(筷子3) P3(哲学家3) -->|请求| F3 P3 -->|请求| F4(筷子4) P4(哲学家4) -->|请求| F4 P4 -->|请求| F5(筷子5) P5(哲学家5) -->|请求| F5 P5 -->|请求| F1

解决方案:

  • 限制最多4位哲学家同时就餐
  • 使用资源分级(筷子编号,总是先拿小号筷子)

页面模块:Message box/ambox.css没有内容。

总结[编辑 | 编辑源代码]

  • 死锁的四个必要条件缺一不可
  • 预防策略围绕破坏必要条件展开
  • 实际开发中应优先使用`java.util.concurrent`工具包
  • 复杂系统建议结合静态分析和运行时检测