死锁的发生原因和怎么避免
                           
天天向上
发布: 2025-07-12 13:29:00

原创
72 人浏览过

死锁(Deadlock)”是并发编程中一种严重的问题,它会导致多个线程互相等待对方释放资源,从而永久阻塞,程序不再前进


一、什么是死锁?

死锁是指两个或多个线程在运行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们将永远等待下去


二、死锁发生的四个必要条件(操作系统定义)

条件含义说明
互斥条件至少一个资源只能被一个线程占用
请求保持条件一个线程持有资源的同时,再请求其他资源
不剥夺条件已获取的资源不能被强制剥夺,只能线程自己释放
循环等待条件若干线程之间形成了资源的循环等待链

只要四个条件同时满足,就可能发生死锁。
打破任何一个条件,就能避免死锁。


三、经典死锁代码示例(Java)

public class DeadlockDemo {
    static final Object lockA = new Object();
    static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("T1 持有 lockA,等待 lockB...");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lockB) {
                    System.out.println("T1 获取 lockB");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("T2 持有 lockB,等待 lockA...");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                synchronized (lockA) {
                    System.out.println("T2 获取 lockA");
                }
            }
        });

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

👆 输出停在两个线程都等待对方时,不会再继续运行 —— 死锁了。


四、死锁的常见发生场景

  1. 多线程持有多个锁且加锁顺序不一致
  2. 数据库事务中,多个连接交叉锁表或记录
  3. 使用 synchronized 时嵌套多个锁(嵌套同步块)
  4. 使用 ReentrantLock.lock() 不加超时处理
  5. 操作系统层面死锁(如文件锁、设备锁)

五、避免死锁的方法

1. 统一加锁顺序(推荐)

保证所有线程按照相同顺序获取资源。

// Always lock A before B
synchronized (lockA) {
    synchronized (lockB) {
        // safe code
    }
}

2. 加锁时使用 tryLock + 超时机制

适用于 ReentrantLock

if (lockA.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lockB.tryLock(1, TimeUnit.SECONDS)) {
            try {
                // safe code
            } finally {
                lockB.unlock();
            }
        }
    } finally {
        lockA.unlock();
    }
}

👉 避免无限等待,可设置回退策略。


3. 避免嵌套锁(不必要不要获取多个锁)

  • 简化代码结构
  • 减少锁的持有时间

4. 使用定时锁、显式锁释放机制

  • 比如使用 LocktryLock() 而不是 lock()
  • 或者使用 synchronized + 尽早释放

5. 使用死锁检测工具

  • jstack 查看线程堆栈状态
  jstack <pid>
  • IntelliJ IDEA、VisualVM 等支持线程图、死锁提示

6. 使用高级并发框架(如 Akka、Actor 模型)

  • 避免线程直接持有共享资源
  • 消息驱动的架构不会产生传统意义上的锁

六、数据库层面的死锁

在数据库中,事务间锁定同一张表的不同记录,也可能死锁。

举例:

  • 事务 A:UPDATE table WHERE id=1
  • 事务 B:UPDATE table WHERE id=2
  • 然后 A 再锁 id=2,B 再锁 id=1 ⇒ 死锁!

解决方法:

  • 统一更新顺序(如 always id 小的先锁)
  • 控制事务范围(越小越好)
  • 检查事务隔离级别(如不使用 SERIALIZABLE

📚 七、参考资料


八、面试提问模拟总结

面试问题答案要点
什么是死锁?如何发生?定义 + 四个必要条件
死锁常见示例?两线程互锁 A、B 的例子
如何避免死锁?加锁顺序统一、tryLock 超时、控制锁粒度等
如何排查死锁?jstack、线程 dump 工具
数据库死锁如何避免?控制锁顺序、减少事务时间

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

发表回复 0

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