在某些應用場景下,可以作到預防死鎖的發生。本文會描述三種情形:html
多個線程須要相同的鎖來完成代碼順暢運行,可是訪問鎖的順序是不一樣的,那麼就可能會發生死鎖的狀況。java
也就是說,若是多個線程須要相同的鎖,可是他們對鎖的訪問順序是相同的,那麼就不可能會出現死鎖的狀況。bash
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
複製代碼
若是如今有一個線程,須要多個鎖(和 Thread 3 相似),它必需要找既定的順序去獲取鎖,它不能在沒獲取前面鎖的狀況下(例如:A)去獲取排在後面的鎖(例如:B)。數據結構
「鎖排序」是一種簡單而有效的死鎖預防機制。可是,只有在獲取任何鎖以前就知道所需的全部鎖才能使用這種方式防護。多線程
另外一個死鎖預防機制是對嘗試「獲取鎖」設置超時時長,這意味着嘗試獲取鎖的線程只會在放棄以前阻塞這麼長的時間。併發
若是一個線程在給定的超時時間內仍沒有成功獲取全部必要的鎖,那麼它將阻塞並釋放全部已持有的鎖,等待一段隨機時間,而後重試。dom
等待的隨機時間用於讓其餘線程嘗試獲取相同的鎖,從而讓應用程序繼續運行而不阻塞。ui
下面的例子就是兩個線程嘗試以不一樣的順序獲取相同的鎖,而後線程阻塞和重試:spa
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.
複製代碼
上例中,Thread 2 比 Thread 1 先 200ms 進行從新嘗試而且在這種狀況下很大可能會成功獲取兩個必要鎖。Thread 1 將繼續等待 lock A。當Thread 2運行結束,Thread 1 也可以獲取兩個鎖。(除非Thread 2 或者其餘線程在又開始插足鎖得獲取)線程
注:鎖超時並不意味着線程已經死鎖,也可能意味着持有鎖的線程(致使另外一個線程超時)須要很長的時間來完成它的任務。
另外,若是有不少的線程競爭相同的資源,他們仍然有可能屢次的同時重試,即便超時和阻塞。這種狀況在兩個線程在重試以前等待0到500ms可能不會發生,可是若是是10到20個線程,那麼狀況就不同了。它和兩個線程等待相用時間或者近乎相同的時間再重試的狀況差很少。
可是,鎖的超時機制並不能在Java的同步代碼塊中設置超時,你將不得不建立自定義鎖類或使用java.util.concurrency包中的一個Java 5併發結構,編寫自定義鎖並不困難,但超出本文範圍,有興趣的自行了解。
死鎖檢測是一種較重的死鎖防護機制,針對沒法獲取鎖順序和鎖超時不可行的狀況。
每當線程takes鎖時,都會在線程和鎖的數據結構中註明。另外,線程requests鎖也會在數據結構中註明。
當線程請求鎖的請求被拒絕時,線程能夠遍歷鎖圖來檢查死鎖。例如,若是線程A請求 lock 7,可是 lock 7被線程B持有,線程A會檢測線程B是否請求線程A已經持有的鎖(若是有)。若是線程B請求是線程A已經持有的鎖,則發生死鎖。
固然,大部分狀況會更加複雜,每每都是多個線程形成死鎖。
那麼發現線程死鎖該如何作呢?
一種可能的操做時釋放全部鎖,阻塞等待一段隨機時間,而後重試。這和更簡單的鎖超時機制相似,只是線程僅在肯定發生死鎖的時候進行阻塞,而不只根據發生鎖超時來判斷。可是,若是不少線程正在爭奪相同的鎖,即便它們阻塞並等待,它們也可能會重複地陷入僵局。
一個更好的選擇是肯定和分配線程的優先級,以便於阻塞一個(或幾個)線程,其他的線程繼續獲取他們所需的鎖,就像沒有死鎖發生同樣。若是分配給線程的優先級是固定的,相同的線程將老是被富裕更高的優先級。爲了不這種狀況,能夠在檢測死鎖時隨機分配優先級。
原文連接: