阿里面試題:如何檢測並避免 Java 中的死鎖?

作者:Yujiaao
來源:https://segmentfault.com/a/1190000019962661

經典但核心Java面試問題之一。

如果你沒有參與過多線程併發 Java 應用程序的編碼,你可能會失敗。

如何避免 Java 線程死鎖?

這是 Java 面試 的熱門問題之一, 也是多線程的編程中的重口味之一, 主要在招高級程序員時容易被問到, 且有很多後續問題。

儘管問題看起來非常基本, 但大多數 Java 開發人員一旦你開始深入, 就會陷入困境。

面試問題總是以“什麼是死鎖🔒?”開始

當兩個或多個線程在等待彼此釋放所需的資源(鎖定)並陷入無限等待即是死鎖。它僅在多任務或多線程的情況下發生。

如何檢測 Java 中的死鎖?

雖然這可以有很多答案, 但我的版本是首先我會看看代碼, 如果我看到一個嵌套的同步塊,或從一個同步的方法調用其他同步方法, 或試圖在不同的對象上獲取鎖, 如果開發人員不是非常小心,就很容易造成死鎖。

另一種方法是在運行應用程序時實際鎖定時找到它, 嘗試採取線程轉儲,在 Linux 中,你可以通過kill -3命令執行此操作, 關注公衆號Java大後端,回覆關鍵字資料,獲取最新架構資料,這將打印應用程序日誌文件中所有線程的狀態, 並且你可以看到哪個線程被鎖定在哪個線程對象上。

你可以使用 fastthread.io 網站等工具分析該線程轉儲, 這些工具允許你上載線程轉儲並對其進行分析。

另一種方法是使用 jConsole 或 VisualVM, 它將顯示哪些線程被鎖定以及哪些對象被鎖定。

如果你有興趣瞭解故障排除工具和分析線程轉儲的過程, 我建議你看看 Uriah Levy 在多元視覺(PluraIsight)上《分析 Java 線程轉儲》課程。旨在詳細瞭解 Java 線程轉儲, 並熟悉其他流行的高級故障排除工具。

編寫一個將導致死鎖的Java程序?

一旦你回答了前面的問題,他們可能會要求你編寫代碼,這將導致Java死鎖。

這是我的版本之一

/**  
 * Java 程序通過強制循環等待來創建死鎖。
 */
public class DeadLockDemo {

/*  
     * 此方法請求兩個鎖,第一個字符串,然後整數  
     */
public void method1() {  
        synchronized (String.class) {  
            System.out.println("Aquired lock on String.class object");

            synchronized (Integer.class) {  
                System.out.println("Aquired lock on Integer.class object");  
            }  
        }  
    }


/*  
     * 此方法也請求相同的兩個鎖,但完全  
     * 相反的順序,即首先整數,然後字符串。
     * 如果一個線程持有字符串鎖,則這會產生潛在的死鎖  
     * 和其他持有整數鎖,他們等待對方,永遠。
     */
public void method2() {  
        synchronized (Integer.class) {  
            System.out.println("Aquired lock on Integer.class object");

            synchronized (String.class) {  
                System.out.println("Aquired lock on String.class object");  
            }  
        }  
    }  
}

如果 method1() 和 method2() 都由兩個或多個線程調用,則存在死鎖的可能性, 因爲如果線程 1 在執行 method1() 時在 Sting 對象上獲取鎖, 線程 2 在執行 method2() 時在 Integer 對象上獲取鎖, 等待彼此釋放 Integer 和 String 上的鎖以繼續進行一步, 但這永遠不會發生。

此圖精確演示了我們的程序, 其中一個線程在一個對象上持有鎖, 並等待其他線程持有的其他對象鎖。

你可以看到, Thread1 需要 Thread2 持有的 Object2 上的鎖,而 Thread2 希望獲得 Thread1 持有的 Object1 上的鎖。由於沒有線程願意放棄, 因此存在死鎖, Java 程序被卡住。

其理念是, 你應該知道使用常見併發模式的正確方法, 如果你不熟悉這些模式,那麼 Jose Paumard 《應用於併發和多線程的常見 Java 模式》是學習的好起點

如何避免Java中的死鎖?

現在面試官來到最後一部分, 在我看來, 最重要的部分之一; 如何修復代碼中的死鎖?或如何避免Java中的死鎖?

如果你仔細查看了上面的代碼,那麼你可能已經發現死鎖的真正原因不是多個線程, 而是它們請求鎖的方式, 如果你提供有序訪問, 則問題將得到解決。

下面是我的修復版本,它通過避免循環等待,而避免死鎖, 而不需要搶佔, 這是需要死鎖的四個條件之一。

public class DeadLockFixed {

/**  
     * 兩種方法現在都以相同的順序請求鎖,首先採用整數,然後是 String。
     * 你也可以做反向,例如,第一個字符串,然後整數,  
     * 只要兩種方法都請求鎖定,兩者都能解決問題  
     * 順序一致。
     */
public void method1() {  
        synchronized (Integer.class) {  
            System.out.println("Aquired lock on Integer.class object");

            synchronized (String.class) {  
                System.out.println("Aquired lock on String.class object");  
            }  
        }  
    }

public void method2() {  
        synchronized (Integer.class) {  
            System.out.println("Aquired lock on Integer.class object");

            synchronized (String.class) {  
                System.out.println("Aquired lock on String.class object");  
            }  
        }  
    }  
}

現在沒有任何死鎖,因爲兩種方法都按相同的順序訪問 Integer 和 String 類文本上的鎖。

因此,如果線程 A 在 Integer 對象上獲取鎖, 則線程 B 不會繼續, 直到線程 A 釋放 Integer 鎖, 即使線程 B 持有 String 鎖, 線程 A 也不會被阻止, 因爲現在線程 B 不會期望線程 A 釋放 Integer 鎖以繼續。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章