Skip to content

如何预防和避免线程死锁?

  • 线程死锁(Deadlock)
  • 是多线程编程中的一种状态,指两个或多个线程相互持有对方所需的资源,同时又等待对方释放,导致所有线程永久阻塞,无法继续执行。

核心点

  • 条件:资源竞争和循环等待。
  • 结果:程序卡死。

1. 死锁详解

(1) 产生条件(四大必要条件)

死锁发生需同时满足以下条件: 1. 互斥条件(Mutual Exclusion): - 资源一次只能被一个线程持有。 2. 请求与保持条件(Hold and Wait): - 线程持有至少一个资源,又请求新资源。 3. 不可抢占条件(No Preemption): - 资源只能由持有者主动释放。 4. 循环等待条件(Circular Wait): - 线程间形成资源请求的闭环。

(2) 示例

public class DeadlockDemo {
    public static void main(String[] args) {
        String lockA = "LockA";
        String lockB = "LockB";

        Thread t1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("T1: Locked A");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockB) {
                    System.out.println("T1: Locked B");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("T2: Locked B");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockA) {
                    System.out.println("T2: Locked A");
                }
            }
        });

        t1.start();
        t2.start();
    }
}
  • 过程
  • T1 持有 lockA,请求 lockB
  • T2 持有 lockB,请求 lockA
  • 双方互相等待,形成死锁。

(3) 输出

T1: Locked A
T2: Locked B
// 程序卡住

2. 死锁的影响

  • 程序行为
  • 线程永久阻塞,任务未完成。
  • 系统资源
  • 占用 CPU、内存但无进展。
  • 用户体验
  • 应用无响应。

3. 检测与诊断

  • 工具
  • JStack:查看线程堆栈。
    • 命令:jstack <pid>
    • 输出:显示 deadlock 和锁信息。
  • JVisualVM:监控线程状态。
  • 特征
  • 线程状态为 BLOCKED,互相等待锁。

示例 JStack 输出

"Thread-1" blocked on lockA owned by "Thread-0"
"Thread-0" blocked on lockB owned by "Thread-1"
Found one Java-level deadlock

4. 预防与解决

(1) 预防方法

  1. 统一锁顺序:破坏循环等待。
  2. 超时机制:避免无限等待。
  3. 减少锁范围:降低竞争。
  4. 避免嵌套锁:简化资源依赖。
  5. 资源分配策略:一次性请求所有资源。
  6. 检测与恢复:监控并中断死锁。

(2) 修改示例

Thread t1 = new Thread(() -> {
    synchronized (lockA) {
        synchronized (lockB) { // 顺序一致
            System.out.println("T1: Locked A and B");
        }
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lockA) {
        synchronized (lockB) { // 顺序一致
            System.out.println("T2: Locked A and B");
        }
    }
});

(3) 使用 Lock

ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

Thread t1 = new Thread(() -> {
    if (lockA.tryLock()) {
        try {
            if (lockB.tryLock()) {
                try {
                    System.out.println("T1: Locked A and B");
                } finally {
                    lockB.unlock();
                }
            }
        } finally {
            lockA.unlock();
        }
    }
});

5. 延伸与面试角度

  • 与活锁对比
  • 死锁:线程卡死。
  • 活锁:线程活跃但无进展(如礼让循环)。
  • 实际应用
  • 数据库:事务死锁。
  • 多线程:资源竞争。
  • 面试点
  • 问“定义”时,提四大条件。
  • 问“解决”时,提锁顺序或 tryLock

总结

线程死锁是多线程因资源循环等待而阻塞的现象,需满足互斥、保持、不可抢占和循环条件。预防靠顺序或超时机制。面试时,可写死锁代码或提 JStack,展示理解深度。