在有些情況下死鎖是可以避免的。本文將展示三種用於避免死鎖的技術。對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並且處於等待
狀態
要注意的是,由於存在鎖的超時,所以我們不能認為這種場景就一定是出現了死鎖。也可能是因為獲得了鎖的執行緒(導致其它執行緒超時)需要很長的時間去完成它的任務。
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中文網其他相關文章!