《java併發編程實戰》讀書筆記——避免死鎖的發生

我們使用加鎖機制來確保線程安全,但如果過度地使用加鎖,則可能導致鎖順序死鎖。當一個線程永遠地持有一個鎖,並且其他線程都嘗試獲得這個鎖時,那麼他們將永遠被阻塞。

在數據庫系統的設中考慮了檢測死鎖以及從死鎖中恢復。但它檢測到一組事務發生了死鎖時(通過在表示等待關係的有向圖中搜索循環),將選擇一個犧牲者並放棄這個事務

JVM在解決死鎖問題方面並沒有數據庫服務那樣強大。當一組線程發生死鎖時,“遊戲”將到此結束。根據線程完成工作的不同,可能造成應用程序完全停止,或者某個特定的子系統停止。

順序死鎖:


死鎖的原因是:兩個線程試圖以不同的順序來獲得相同的鎖。

如果按照相同的順序來請求鎖,那麼就不會出現循環的加鎖依賴性,因此也就不會產生死鎖。


但是有時候,並不能清楚地知道是否在鎖順序上有足夠的控制權來避免死鎖的發生。

如考慮以下情景:

銀行轉賬行爲,需要將資金由一個賬戶轉入另一個賬戶。在開始轉賬前要先獲得這兩個賬戶的Acount對象的鎖,以確保通過原子的方式來更新兩個賬戶中的餘額。

public void transferMoney(Acoount fromAccount,Account toAccount,int amount){
	synchronized(fromAccount){
		synchronized(toAccount){
			...
			transfer money from fromAccount to toAccount
			...
		}
	}
}

以上是線程不安全的,如果兩個線程同時調用transferMoney,其中一個線程從X向Y轉賬,另一個線程從Y向X轉賬,那麼就會發生死鎖。

解決方式是:可以使用System.identityHashCode,該方法將返回由object.hashCode返回的值。

修改以上代碼如下:

public void transferMoney(Acoount fromAccount,Account toAccount,int amount){
	int fromHash = System.identityHashCode(fromAccount);
	int toHash = System.identityHashCode(toAccount);
	if(fromHash > toHash){
		synchronized(fromAccount){
			synchronized(toAccount){
				...
				transfer money from fromAccount to toAccount
				...
			}
		}
	}else{
		synchronized(toAccount){
			synchronized(fromAccount){
				...
				transfer money from fromAccount to toAccount
				...
			}
		}
	}
}

這樣無論傳入參數的位置如何,獲得鎖的順序都是一樣的。

在協作對象之間發生死鎖

某些必須獲得多個鎖的操作並不會像之前transferMoney中那麼明顯,這兩個鎖不一定必須在同一個方法中獲取。
所以如果在持有某個鎖的同時,又去調用某個外部方法,那麼就有可能發生問題,在這個外部方法中有可能會去嘗試獲得另一個鎖。這樣就會有發生
死鎖的風險。
所以,如果在調用某個方法的時候不需要持有鎖,那麼這種調用就被稱爲開發調用,可以有效的避免上面這種情況發生的死鎖。

資源死鎖

當多個線程相互持有彼此正在等待的鎖而又不釋放自己已持有的鎖時會發生死鎖, 當他們在相同的資源集合上等待時,也會發生死鎖

死鎖的避免與診斷

如果必須獲得多個鎖,那麼在設計時必須考慮鎖的順序:儘量減少潛在的而加鎖交互數量,將獲取鎖時需要遵循的協議寫入正式的文檔並始終遵循這些協議

必要時候使用支持定時的鎖及顯示的使用Lock類中的定時tryLock功能來代替內置鎖機制。當使用內置鎖時,只要沒有獲得鎖,就會永遠的等待下去,而顯示鎖則可以指定一個超時時限,在等待了一定時間以後,如果還沒有成功獲得鎖,那麼久放棄,並返回一個失敗信息。

還可以通過線程的轉儲信息來分析死鎖發生的信息。


發佈了40 篇原創文章 · 獲贊 32 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章