问答题992/1053什么是线程死锁?如何避免死锁?

难度:
2021-11-02 创建

参考答案:

线程死锁

线程死锁是指两个或多个线程在执行过程中,由于竞争资源而造成的一种互相等待的现象。每个线程都在等待对方持有的资源,从而导致所有线程无法继续执行,进入一种“僵尸”状态。死锁的发生会导致系统性能下降,甚至完全停滞。

死锁的四个必要条件

死锁的发生通常是由以下四个条件共同作用引起的,这四个条件被称为“死锁的必要条件”:

  1. 互斥条件(Mutual Exclusion):资源一次只能被一个线程占用。若一个线程正在使用某个资源,其他线程只能等待该资源的释放。

  2. 占有且等待(Hold and Wait):一个线程在持有某些资源的同时,还在等待其他线程释放它所需要的资源。

  3. 非抢占条件(No Preemption):资源不能被抢占,只有当线程释放资源时,其他线程才能获取这些资源。

  4. 循环等待(Circular Wait):若干线程形成一种闭环的等待关系,即线程A等待线程B持有的资源,线程B等待线程C持有的资源,线程C又等待线程A持有的资源。

死锁的例子

1class A { 2 synchronized void methodA(B b) { 3 System.out.println("Thread A: Holding lock A..."); 4 try { Thread.sleep(100); } catch (InterruptedException e) {} 5 System.out.println("Thread A: Waiting for lock B..."); 6 b.last(); 7 } 8 9 synchronized void last() { 10 System.out.println("Thread A: Released lock A."); 11 } 12} 13 14class B { 15 synchronized void methodB(A a) { 16 System.out.println("Thread B: Holding lock B..."); 17 try { Thread.sleep(100); } catch (InterruptedException e) {} 18 System.out.println("Thread B: Waiting for lock A..."); 19 a.last(); 20 } 21 22 synchronized void last() { 23 System.out.println("Thread B: Released lock B."); 24 } 25} 26 27public class DeadlockExample { 28 public static void main(String[] args) { 29 A a = new A(); 30 B b = new B(); 31 32 Thread t1 = new Thread(() -> a.methodA(b)); 33 Thread t2 = new Thread(() -> b.methodB(a)); 34 35 t1.start(); 36 t2.start(); 37 } 38}

在上面的代码中,线程 t1 和线程 t2 会相互等待对方释放锁,从而导致死锁。

如何避免死锁

避免死锁的方法包括但不限于以下几种:

1. 避免嵌套锁

尽量避免在多个资源上获取锁。若多个线程必须同时访问多个资源,可以按照一定顺序请求锁,确保所有线程按照相同的顺序请求资源,从而避免循环等待。

例如,可以规定线程始终按以下顺序获取资源:

  • 线程A先获取资源1,再获取资源2。
  • 线程B先获取资源2,再获取资源1。

通过规范获取锁的顺序,可以避免死锁的发生。

2. 锁的超时机制(Timeout)

在获取锁时设置超时机制,如果超时未获取到锁,则放弃当前操作。这样可以避免线程因等待锁而一直阻塞,降低死锁发生的概率。

1public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 2 return lock.tryLock(time, unit); 3}

3. 使用 lock 替代 synchronized

Lock 接口比 synchronized 更加灵活,可以通过 tryLock() 方法实现尝试获取锁而不阻塞,从而避免死锁。

4. 死锁检测和恢复

可以使用专门的算法或工具定期检查是否存在死锁。例如,某些数据库和分布式系统会监控线程是否进入死锁状态,及时干预或恢复。

5. 最小化锁的持有时间

确保持有锁的时间尽量短。一个线程持有锁的时间越长,发生死锁的概率就越大。优化代码,减少在持有锁期间的操作,提高锁的释放效率。

6. 使用更高层次的并发工具

如 Java 提供的 ExecutorServiceCountDownLatchSemaphore 等工具类,它们可以帮助协调线程之间的操作,减少手动管理锁的复杂度,从而降低死锁的风险。

最近更新时间:2024-12-06