基于本文回答
0
评论

Java中产生死锁的四个必要条件是什么?

知识点图片

在Java(以及任何并发编程环境)中,产生死锁必须同时满足以下四个必要条件。这四个条件被称为 Coffman 条件,只要打破其中任何一个,就可以避免死锁的发生:

1. 互斥条件 (Mutual Exclusion)

  • 含义:资源在同一时刻只能被一个线程占用。如果其他线程想要获取该资源,就必须等待,直到占有该资源的线程释放。
  • Java中的体现:使用 synchronized 关键字或者 ReentrantLock 获取的排他锁。同一时间只能有一个线程持有该锁。

2. 请求与保持条件 (Hold and Wait / 占有并等待)

  • 含义:一个线程已经持有了一个(或多个)资源,但又提出了新的资源请求,而新请求的资源正被其他线程占用。此时该线程被阻塞,但对自己已经获得的资源保持不放
  • Java中的体现:线程 A 已经进入了 synchronized(lock1) 代码块,在没有退出(即没有释放 lock1)的情况下,又试图进入 synchronized(lock2) 代码块,但 lock2 此时被别的线程持有。

3. 不剥夺条件 (No Preemption / 非抢占)

  • 含义:线程已经获得的资源在未使用完之前,不能被其他线程强行剥夺(抢占),只能由持有该资源的线程主动释放
  • Java中的体现:在Java中,无论是内部锁 (synchronized) 还是显式锁 (Lock),操作系统和JVM都不会强行把锁从一个正在运行的线程手中夺走交给另一个线程,只能等待持有锁的线程执行完毕或抛出异常来释放锁。

4. 循环等待条件 (Circular Wait)

  • 含义:发生死锁时,必然存在一个线程——资源的循环链。即:线程T1等待线程T2占有的资源,线程T2等待线程T3占有的资源……直到线程Tn等待线程T1占有的资源,形成一个闭环。
  • Java中的体现:最经典的场景:线程 A 持有锁 L1,尝试获取锁 L2;同时线程 B 持有锁 L2,尝试获取锁 L1。两者互相等待对方释放锁,形成死循环。

💡 附加知识:如何在Java中破坏这四个条件来避免死锁?

在实际开发中,我们只要破坏上述四个条件中的任意一个,就能解除或避免死锁:

  1. 破坏互斥条件
    • 通常很难破坏,因为锁的目的就是为了互斥。但在某些场景下,可以使用无锁并发数据结构(如 ConcurrentHashMap)或原子变量(如 AtomicInteger、CAS机制)来代替传统锁。
  2. 破坏请求与保持条件
    • 规定线程在执行前,一次性申请所有需要的资源。如果有任何一个资源拿不到,就什么都不拿。
  3. 破坏不剥夺条件
    • 使用 java.util.concurrent.locks.Lock 接口中的 tryLock() 方法。如果一个线程持有了部分锁,再去尝试获取其他锁时超时或失败,则主动释放自己目前已占用的所有锁,退一步海阔天空。
  4. 破坏循环等待条件(最常用、最有效的方法)
    • 按序加锁:规定一个系统内所有线程获取锁的顺序。例如,强制规定所有线程必须先获取 L1 锁,才能获取 L2 锁。这样就永远不会出现 A 拿 L1 等 L2,B 拿 L2 等 L1 的闭环情况。
右滑查看面试常问