阿里面试:线程死锁是什么?如何解决线程死锁?

线程死锁经常被大厂考察,下面我来详解线程死锁,以及解决方案@mikechen

线程死锁是什么

线程死锁指的是:两个或多个线程在执行过程中,因争夺资源而形成一种相互等待的局面,导致彼此都无法继续执行。

阿里面试:线程死锁是什么?如何解决线程死锁?-mikechen

最常见的场景是多个线程按不同顺序获取多个锁。

例如:

  • 线程 1 先拿到锁 A,再尝试拿锁 B;
  • 线程 2 先拿到锁 B,再尝试拿锁 A;

如果线程 1 和线程 2 同时执行,就可能出现:

  • 线程 1 持有 A,等待 B;
  • 线程 2 持有 B,等待 A;

此时双方都在等待对方释放锁,程序进入死锁状态。

 

多线程死锁

阿里面试:线程死锁是什么?如何解决线程死锁?-mikechen

1. 统一加锁顺序

这是最常用、最有效的方法。

规定所有线程必须按照相同的顺序获取锁,例如都先拿 A 再拿 B,这样就可以避免循环等待。

// 线程 1
synchronized (lockA) {
    synchronized (lockB) {
        // ...
    }
}

// 线程 2
synchronized (lockA) {
    synchronized (lockB) {
        // ...
    }
}

 

2. 减少锁的持有时间

尽量缩小同步代码块的范围,避免在持有锁时执行耗时操作,如:

  • 网络请求;
  • 数据库访问;
  • 文件读写;
  • 复杂计算;

锁持有时间越短,死锁概率越低,比如:

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

public boolean execute() throws InterruptedException {
    if (!lockA.tryLock(500, TimeUnit.MILLISECONDS)) {
        return false;
    }

    try {
        if (!lockB.tryLock(500, TimeUnit.MILLISECONDS)) {
            return false;
        }

        try {
            // 执行业务逻辑
            return true;
        } finally {
            lockB.unlock();
        }
    } finally {
        lockA.unlock();
    }
}

 

3. 避免嵌套锁

尽量不要在一个锁内部,再申请另一个锁,比如:

synchronized (lockA) {
    methodB(); // methodB 内部又获取 lockB
}

更好的方式是,尽量只持有一把锁。

不要在持锁期间调用外部方法,不要在持锁期间执行网络请求、RPC、磁盘 I/O。

评论交流
    说说你的看法