Java執行緒面試題(02) Java執行緒中如何避免死鎖

劉近光發表於2017-11-21

本文為本博主翻譯,未經允許,嚴禁轉載!

簡介

如何避免Java中的死鎖?是多執行緒程式設計常見問題之一,在高階別的面試中經常被問及,並帶來了大量的後續問題。儘管問題看起來很基本,但是一旦開始深入,大部分開發者都會陷入困境。
面試問題從“什麼是死鎖?”開始。答案很簡單,當有兩個或更多的執行緒在等待對方釋放鎖並無限期地卡住時,這種情況就稱為死鎖。這隻會發生在多執行緒的情況下。

Java中如何監測死鎖

雖然這可能有很多的答案,我的版本是首先我會看程式碼,如果我看到巢狀的同步塊或從其他呼叫一個同步方法或嘗試鎖定不同的物件,那麼如果開發人員不是非常小心,很有可能發生死鎖。
其他方法是在執行應用程式時實際上被鎖定的時候找到它,試著對執行緒進行轉儲,在Linux中你可以通過命令“kill -3”來做到這一點,這將在應用程式日誌檔案中列印所有執行緒的狀態,而你可以看到哪個執行緒被鎖定在哪個物件上。

其他的方法是使用jconsole,它會告訴你究竟哪個執行緒被鎖定在哪個物件上。

編寫一個會導致死鎖的Java程式

一旦你回答了這個問題,他們可能會要求你編寫會導致死鎖的程式碼?這是我的一個版本。

/**
 * * Java program to create a deadlock by imposing circular wait. * * @author
 * WINDOWS 8 *
 */
public class DeadLockDemo {
	/* 
	 * This method request two locks, first String and then Integer 
	 **/
	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");
			}
		}
	}

	/*
	 * * This method also requests same two lock but in exactly * Opposite order
	 * i.e. first Integer and then String. * This creates potential deadlock, if
	 * one thread holds String lock * and other holds Integer lock and they wait
	 * for each other, forever.
	 */
	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鎖,這將永遠不會發生。
這個圖完全展示了我們的程式,其中一個執行緒持有一個物件的鎖,並等待其他執行緒持有的其他物件鎖。

如何避免Java中的死鎖?

現在面試官來到最後一部分,我認為最重要的部分之一: 你如何解決死鎖?或者如何避免Java中的死鎖?
如果你仔細看過上面的程式碼,那麼你可能已經發現死鎖的真正原因並不是多執行緒,而是它們請求鎖定的方式,如果你提供了一個有序的訪問,那麼問題就會解決,這裡是我的修復死鎖問題的版本,它可以通過在沒有搶佔的情況下避免迴圈等待從而解決死鎖問題。

public class DeadLockFixed {
	/**
	 * * Both method are now requesting lock in same order, first Integer and
	 * then String. * You could have also done reverse e.g. first String and
	 * then Integer, * both will solve the problem, as long as both method are
	 * requesting lock * in consistent order.
	 */
	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鎖繼續前進。

原文連結

How to avoid deadlock in Java Threads

相關文章