首頁  >  文章  >  Java  >  如何使Java避免死鎖的範例程式碼分享

如何使Java避免死鎖的範例程式碼分享

黄舟
黄舟原創
2017-06-18 09:48:231425瀏覽

在有些情況下死鎖是可以避免的。本文將展示三種用於避免死鎖的技術。對java避免死鎖的相關知識感興趣的朋友一起透過本文學習吧

在有些情況下死鎖是可以避免的。本文將展示三種用於避免死鎖的技術:

1.加鎖順序

2.加鎖時限

#3.死鎖偵測

#加上鎖順序

當多個執行緒需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生。

如果能確保所有的執行緒都是按照相同的順序獲得鎖,那麼死鎖就不會發生。看下面這個例子:

Thread 1:
 lock A 
 lock B
Thread 2:
  wait for A
  lock C (when A locked)
Thread 3:
  wait for A
  wait for B
  wait for C

如果一個執行緒(例如執行緒3)需要一些鎖,那麼它必須按照確定的順序取得鎖。它只有獲得了從順序上排在前面的鎖之後,才能取得後面的鎖。

例如,執行緒2和執行緒3只有在取得了鎖A之後才能嘗試取得鎖定C(取得鎖定A是取得鎖定C的必要條件)。因為線程1已經擁有了鎖A,所以線程2和3需要一直等到鎖A被釋放。然後在它們嘗試對B或C加鎖之前,必須成功地對A加了鎖。

依照順序加鎖是一種有效的死鎖預防機制。但是,這種方式需要你事先知道所有可能會用到的鎖(並對這些鎖做適當的排序),但總有些時候是無法預知的。

加上鎖定時限

#另外一個可以避免死鎖的方法是在嘗試取得鎖定的時候加上一個逾時時間,這也意味著在嘗試取得鎖的過程中若超過了這個時限該執行緒就放棄對該鎖定請求。若一個執行緒沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖,然後等待一段隨機的時間再重試。這段隨機的等待時間讓
其它

線程有機會嘗試獲取相同的這些鎖,並且讓該應用在沒有獲得鎖的時候可以繼續運行(加鎖超時後可以先繼續運行乾點其它事情,再回頭重複之前加鎖的邏輯)。

以下是一個例子,展示了兩個執行緒以不同的順序嘗試取得相同的兩個鎖,在發生逾時後退並重試的場景:

 Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.
Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.
在上面的範例中,執行緒2比執行緒1早200毫秒進行重試加鎖,因此它可以先成功地取得到兩個鎖定。這時,執行緒1嘗試取得鎖定A並且處於等待
狀態

。當線程2結束時,線程1也可以順利的獲得這兩個鎖(除非線程2或者其它線程在線程1成功獲得兩個鎖之前又獲得其中的一些鎖)。

要注意的是,由於存在鎖的超時,所以我們不能認為這種場景就一定是出現了死鎖。也可能是因為獲得了鎖的執行緒(導致其它執行緒超時)需要很長的時間去完成它的任務。

此外,如果有非常多的線程同一時間去競爭同一批資源,就算有超時和回退機制,還是可能會導致這些線程重複地嘗試但卻始終得不到鎖。如果只有兩個線程,並且重試的超時時間設定為0到500毫秒之間,這種現象可能不會發生,但是如果是10個或20個線程情況就不同了。因為這些執行緒等待相等的重試時間的機率就高的多(或非常接近以至於會出現問題)。 (超時和重試機制是為了避免在同一時間出現的競爭,但是當線程很多時,其中兩個或多個線程的超時時間一樣或者接​​近的可能性就會很大,因此就算出現競爭而導致超時後,由於超時時間一樣,它們又會同時開始重試,導致新一輪的競爭,帶來了新的問題。對synchronized同步區塊設定逾時時間。你需要建立一個自訂鎖,或使用Java5中java.util.con

current套件下的工具。寫一個自訂鎖類別不複雜,但超出了本文的內容。

死鎖偵測

死鎖偵測是一個更好的死鎖預防機制,它主要是針對那些不可能實現依序加鎖並且鎖逾時也不可行的場景。

每當一個執行緒獲得了鎖,就會在執行緒和鎖相關的資料結構中(map、graph等等)將其記下來。除此之外,每當有線程請求鎖,也需要記錄在這個資料結構中。

當一個執行緒請求鎖定失敗時,這個執行緒可以遍歷鎖的關係圖看看是否有死鎖發生。例如,線程A請求鎖7,但是鎖7這個時候被線程B持有,這時線程A就可以檢查線程B是否已經請求了線程A目前所持有的鎖。如果線程B確實有這樣的請求,那麼就是發生了死鎖(線程A擁有鎖1,請求鎖7;線程B擁有鎖7,請求鎖1)。

當然,死鎖一般要比兩個執行緒互相持有對方的鎖這種情況要複雜的多。線程A等待線程B,線程B等待線程C,線程C等待線程D,線程D又在等待線程A。線程A為了偵測死鎖,它需要遞進地偵測所有被B要求的鎖。從線程B所請求的鎖開始,線程A找到了線程C,然後又找到了線程D,發現線程D請求的鎖被線程A自己持有。這是它就知道發生了死鎖。
那麼當偵測出死鎖時,這些執行緒該做些什麼呢?

一個可行的做法是釋放所有鎖,回退,並且等待一段隨機的時間後重試。這個和簡單的加鎖逾時類似,不一樣的是只有死鎖已經發生了才回退,而不會是因為加鎖的請求超時了。雖然有回退和等待,但是如果有大量的線程競爭同一批鎖,它們還是會重複地死鎖。

一個更好的方案是給這些執行緒設定優先權,讓一個(或幾個)執行緒回退,剩下的執行緒就像沒發生死鎖一樣繼續保持著它們需要的鎖。如果賦​​予這些執行緒的優先權是固定不變的,同一批執行緒總是會擁有更高的優先權。為避免這個問題,可以在死鎖發生的時候設定隨機的優先權。

以上是如何使Java避免死鎖的範例程式碼分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn