1. 程式人生 > 程式設計 >Java中的執行緒死鎖是什麼?如何避免?

Java中的執行緒死鎖是什麼?如何避免?

認識執行緒死鎖

多個執行緒同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於執行緒被無限期地阻塞,因此程式不可能正常終止。

如下圖所示,執行緒 A 持有資源 2,執行緒 B 持有資源 1,他們同時都想申請對方的資源,所以這兩個執行緒就會互相等待而進入死鎖狀態。

Java中的執行緒死鎖是什麼?如何避免?

下面通過一個例子來說明執行緒死鎖,程式碼模擬了上圖的死鎖的情況 (程式碼來源於《併發程式設計之美》):

public class DeadLockDemo {
 private static Object resource1 = new Object();//資源 1
 private static Object resource2 = new Object();//資源 2

 public static void main(String[] args) {
  new Thread(() -> {
   synchronized (resource1) {
    System.out.println(Thread.currentThread() + "get resource1");
    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println(Thread.currentThread() + "waiting get resource2");
    synchronized (resource2) {
     System.out.println(Thread.currentThread() + "get resource2");
    }
   }
  },"執行緒 1").start();

  new Thread(() -> {
   synchronized (resource2) {
    System.out.println(Thread.currentThread() + "get resource2");
    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println(Thread.currentThread() + "waiting get resource1");
    synchronized (resource1) {
     System.out.println(Thread.currentThread() + "get resource1");
    }
   }
  },"執行緒 2").start();
 }
}

Output

Thread[執行緒 1,5,main]get resource1
Thread[執行緒 2,main]get resource2
Thread[執行緒 1,main]waiting get resource2
Thread[執行緒 2,main]waiting get resource1

執行緒 A 通過 synchronized (resource1) 獲得 resource1 的監視器鎖,然後通過Thread.sleep(1000);讓執行緒 A 休眠 1s 為的是讓執行緒 B 得到執行然後獲取到 resource2 的監視器鎖。執行緒 A 和執行緒 B 休眠結束了都開始企圖請求獲取對方的資源,然後這兩個執行緒就會陷入互相等待的狀態,這也就產生了死鎖。上面的例子符合產生死鎖的四個必要條件。

學過作業系統的朋友都知道產生死鎖必須具備以下四個條件:

  1. 互斥條件:該資源任意一個時刻只由一個執行緒佔用。
  2. 請求與保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:執行緒已獲得的資源在末使用完之前不能被其他執行緒強行剝奪,只有自己使用完畢後才釋放資源。
  4. 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係。

如何避免執行緒死鎖?

我們只要破壞產生死鎖的四個條件中的其中一個就可以了。

  • 破壞互斥條件

這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。

  • 破壞請求與保持條件

一次性申請所有的資源。

  • 破壞不剝奪條件

佔用部分資源的執行緒進一步申請其他資源時,如果申請不到,可以主動釋放它佔有的資源。

  • 破壞迴圈等待條件

靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞迴圈等待條件。

我們對執行緒 2 的程式碼修改成下面這樣就不會產生死鎖了。

new Thread(() -> {
   synchronized (resource1) {
    System.out.println(Thread.currentThread() + "get resource1");
    try {
     Thread.sleep(1000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println(Thread.currentThread() + "waiting get resource2");
    synchronized (resource2) {
     System.out.println(Thread.currentThread() + "get resource2");
    }
   }
  },"執行緒 2").start();

Output

Thread[執行緒 1,main]get resource1
Thread[執行緒 1,main]waiting get resource2
Thread[執行緒 1,main]get resource2
Thread[執行緒 2,main]get resource2

Process finished with exit code 0

我們分析一下上面的程式碼為什麼避免了死鎖的發生?

執行緒 1 首先獲得到 resource1 的監視器鎖,這時候執行緒 2 就獲取不到了。然後執行緒 1 再去獲取 resource2 的監視器鎖,可以獲取到。然後執行緒 1 釋放了對 resource1、resource2 的監視器鎖的佔用,執行緒 2 獲取到就可以執行了。這樣就破壞了破壞迴圈等待條件,因此避免了死鎖。

以上就是Java中的執行緒死鎖是什麼?如何避免?的詳細內容,更多關於Java 執行緒死鎖的資料請關注我們其它相關文章!