Skip to content

死锁


一、是什么?

死锁是多线程编程中的一种状态,当两个或多个线程永久阻塞,互相等待对方释放资源时发生。此时程序无法继续执行,需要外部干预才能恢复。

二、产生死锁的条件(什么情况下会产生?)

死锁需同时满足以下四个必要条件:

  1. 互斥:资源一次只能被一个线程占用(如 synchronized 锁)。
  2. 占有并等待:线程持有资源的同时,请求其他线程占有的资源。
  3. 不可剥夺:资源只能由持有者主动释放,不能被强制抢占。
  4. 循环等待:线程间形成环形等待链(如线程A等B的资源,线程B等A的资源)。

三、如何解决死锁

  1. 预防策略(破坏必要条件):

    • 破坏互斥:使用无锁数据结构(如 ConcurrentHashMap)。
    • 破坏占有并等待:一次性申请所有所需资源(通过统一加锁)。
    • 破坏不可剥夺:设定超时机制(如 Lock.tryLock(timeout))。
    • 破坏循环等待:按固定顺序获取资源(如统一按资源ID从小到大加锁)。
  2. 检测与恢复

    • 死锁检测:通过监控线程状态和资源依赖图识别死锁(JDK工具:jstackThreadMXBean.findDeadlockedThreads())。
    • 强制恢复:终止部分线程或回滚操作(需设计事务机制)。
  3. 避免策略

    • 银行家算法:分配资源前预判安全性(实际开发较少用)。
    • 使用高层工具
      • JDK 5+ 的 java.util.concurrent 包(如 Semaphore, CountDownLatch)。
      • JDK 8+ 的 CompletableFuture 异步编程(减少显式锁竞争)。

四、应用场景

  1. 数据库事务中的多表更新。
  2. 多线程资源竞争(如转账:线程A锁账户1等账户2,线程B锁账户2等账户1)。
  3. 分布式系统中的跨服务资源协调。

五、Java示例

java
// 死锁示例
public class DeadlockDemo {
    static final Object lock1 = new Object();
    static final Object lock2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                try { Thread.sleep(100); } 
                catch (InterruptedException e) {}
                synchronized (lock2) {} // 等待lock2
            }
        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                synchronized (lock1) {} // 等待lock1
            }
        }).start();
    }
}
java
// 解决方案:按固定顺序加锁
public class FixedOrderLock {
    static final Object lock1 = new Object();
    static final Object lock2 = new Object();

    public static void main(String[] args) {
        // 所有线程统一先锁lock1,再锁lock2
        new Thread(() -> {
            synchronized (lock1) {
                synchronized (lock2) {} // 安全
            }
        }).start();

        new Thread(() -> {
            synchronized (lock1) {
                synchronized (lock2) {} // 安全
            }
        }).start();
    }
}

六、重要注意事项

  1. 避免嵌套锁:尽量减少锁的嵌套层级。
  2. 锁超时机制:使用 ReentrantLock.tryLock(5, TimeUnit.SECONDS) 替代 synchronized
  3. 代码审查:定期用 jstack 或可视化工具(如JConsole)检测死锁。
  4. 线程池管理:合理设置线程数量,避免资源过度竞争。

七、总结

死锁由互斥、占有等待、不可剥夺和循环等待四个条件共同触发。解决方案包括破坏必要条件、使用超时锁、按顺序加锁、以及采用JDK5+的高并发工具。关键是通过设计规避资源竞争环路,并利用现代并发API降低风险。