五、線程同步之死鎖和活鎖


死鎖和活鎖現象

死鎖
死鎖現象
死鎖:兩個或多個線程相互等待對方釋放鎖,則會出現死鎖現象。java虛擬機沒有檢測,也沒有采用措施來處理死鎖情況,所以多線程編程是應該采取措施避免死鎖的出現。一旦出現死鎖,整個程序即不會發生任何異常,也不會給出任何提示,只是所有線程都處於堵塞狀態。死鎖情況如下圖所示。
下面代碼中有兩個對象作為鎖,兩個線程,線程1先持有A,請求B;線程2先持有B,請求A,導致兩條線程互不相讓,導致卡死狀態。
   
   
   
           
  1. public class Main {
  2. static Object lockA = new Object();
  3. static Object lockB = new Object();
  4. public static void main(String[] args) {
  5. new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. synchronized (lockA) {
  9. System.out.println("第一個線程調用A");
  10. try {
  11. Thread.sleep(1);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. synchronized (lockB) {
  16. System.out.println("第一個線程調用B");
  17. }
  18. }
  19. }
  20. }).start();
  21. new Thread(new Runnable() {
  22. @Override
  23. public void run() {
  24. synchronized (lockB) {
  25. System.out.println("第二個線程調用B");
  26. try {
  27. Thread.sleep(1);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. synchronized (lockA) {
  32. System.out.println("第二個線程調用A");
  33. }
  34. }
  35. }
  36. }).start();
  37. }
  38. }
避免和打破死鎖狀態
形成死鎖的條件
  1. 互斥條件:線程使用的資源必須至少有一個是不能共享的(至少有鎖);
  2. 請求與保持條件:至少有一個線程必須持有一個資源並且正在等待獲取一個當前被其它線程持有的資源(至少兩個線程持有不同鎖,又在等待對方持有鎖);
  3. 非剝奪條件:分配資源不能從相應的線程中被強制剝奪(不能強行獲取被其他線程持有鎖);
  4. 循環等待條件:第一個線程等待其它線程,后者又在等待第一個線程(線程A等線程B;線程B等線程C;...;線程N等線程A。如此形成環路)。
死鎖預防
  1. 加鎖順序

    當多個線程需要相同的一些鎖,但是按照不同的順序加鎖,死鎖就很容易發生。如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會發生。這種方式是一種有效的死鎖預防機制。但是,這種方式需要你事先知道所有可能會用到的鎖,但總有些時候是無法預知的。

  2. 加鎖時限
    在嘗試獲取鎖的時候加一個超時時間,這也就意味着在嘗試獲取鎖的過程中若超過了這個時限該線程則放棄對該鎖請求。若一個線程沒有在給定的時限內成功獲得所有需要的鎖,則會進行回退並釋放所有已經獲得的鎖。
  3. 死鎖檢測(檢測死鎖的比如有jstack)
    死鎖檢測是一個更好的死鎖預防機制,它主要是針對那些不可能實現按序加鎖並且鎖超時也不可行的場景。每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(map等等)將其記下。除此之外,每當有線程請求鎖,也需要記錄在這個數據結構中。檢測到死鎖之后:
    一個可行的做法是釋放所有鎖,回退,並且等待一段隨機的時間后重試。這個和簡單的加鎖超時類似,不一樣的是只有死鎖已經發生了才回退,而不會是因為加鎖的請求超時了。雖然有回退和等待,但是如果有大量的線程競爭同一批鎖,它們還是會重復地死鎖

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

打破死鎖
這里不做討論和驗證,原則是形成死鎖條件的四個必要條件打破任意一個條件,就可以打開死鎖。
活鎖
任務或者執行者沒有被阻塞,由於某些條件沒有滿足,導致一直重復嘗試,失敗,嘗試,失敗。 活鎖 死鎖 的區別在於,處於活鎖的實體是在不斷的改變狀態,所謂的 , 而處於死鎖的實體表現為等待;活鎖有可能自行解開,死鎖則不能。
活鎖的幾個例子
單一實體的活鎖
例如線程從隊列中拿出一個任務來執行,如果任務執行失敗,那么將任務重新加入隊列,繼續執行。假設任務總是執行失敗,或者某種依賴的條件總是不滿足,那么線程一直在繁忙卻沒有任何結果。
協同導致的活鎖
生活中的典型例子: 兩個人在窄路相遇,同時向一個方向避讓,然后又向另一個方向避讓,如此反復。
通信中也有類似的例子,多個用戶共享信道(最簡單的例子是大家都用對講機),同一時刻只能有一方發送信息。發送信號的用戶會進行沖突檢測, 如果發生沖突,就選擇避讓,然后再發送。 假設避讓 算法不合理,就導致每次發送,都沖突,避讓后再發送,還是沖突。
計算機中的例子:兩個線程發生了某些條件的碰撞后重新執行,那么如果再次嘗試后依然發生了碰撞,長此下去就有可能發生活鎖。
活鎖的解決方法
解決協同活鎖的一種方案是調整重試機制。
比如引入一些隨機性。例如如果檢測到沖突,那么就暫停隨機的一定時間進行重試。這回大大減少碰撞的可能性。 典型的例子是以太網的CSMA/CD檢測機制。
另外為了避免可能的死鎖,適當加入一定的重試次數也是有效的解決辦法。盡管這在業務上會引起一些復雜的邏輯處理。
比如約定重試機制避免再次沖突。 例如自動駕駛的防碰撞系統(假想的例子),可以根據序列號約定檢測到相撞風險時,序列號小的飛機朝上飛, 序列號大的飛機朝下飛。






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM