跳转到内容

死锁原理与预防

来自代码酷

模板: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"

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

持有 lock1\n请求 lock2
持有 lock2\n请求 lock1
ThreadA
ThreadB

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

策略 实现方法 示例
破坏互斥条件 使用共享资源(如读写锁) `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;

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

经典同步问题演示死锁:

请求
请求
请求
请求
请求
请求
请求
请求
请求
请求
哲学家1
筷子1
筷子2
哲学家2
筷子3
哲学家3
筷子4
哲学家4
筷子5
哲学家5

解决方案:

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

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

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

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