多線程併發 (六) 瞭解死鎖

總結:

  1. 多線程併發 (一) 瞭解 Java 虛擬機 - JVM 學習知道了創建一個線程會觸發JVM創建一個私有的虛擬機棧、程序計數器,當前的虛擬機棧內存就是當前線程的運行內存,程序計數器就是記錄當前線程運行的代碼的地址。
  2. 多線程併發 (二) 瞭解 Thread 學習了線程的運行狀態、線程的創建方式、調度原理等。
  3. 多線程併發 (三) 鎖 synchronized、volatile 學習知道了多個線程在競爭資源的時候產生的影響、怎麼解決併發問題、synchronized的用法、synchronized鎖的升級、synchronzied原理爲什麼鎖的是Object對象、Object對象在內存的分佈、volatile解決的問題以及錯誤使用。
  4. 多線程併發 (四) 瞭解原子類 AtomicXX 屬性地址偏移量,CAS機制 學習知道了原子類型的源碼實現、對象地址偏移量的理解、CAS機制。
  5. 多線程併發 (五) ReentrantLock 使用和源碼 學習知道了 ReentrantLock 的用法、ReentrantLock類型、源碼實現類、原理AQS的隊列操作。

經過對以上章節的學習,對於正常併發項目遇到問題應該都能解決了,並且學習了鎖的原理,線程的運行狀態,調度方式等。接下來就引入使用鎖來解決併發的同時鎖帶來的問題 ‘死鎖’。

1.什麼是死鎖

百度百科說死鎖是指兩個或兩個以上的線程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

簡單舉例如果線程A鎖住了記錄1並等待記錄2,而線程B鎖住了記錄2並等待記錄1,這樣兩個線程就發生了死鎖現象。

2.死鎖產生的條件

從這個例子中線程A鎖住了記錄1並等待記錄2,而線程B鎖住了記錄2並等待記錄1,這樣兩個線程就發生了死鎖現象。我們能得到一些條件

  1. 互斥條件:同一時間內資源只能被一個線程訪問即 A 佔用了記錄1,B就不能佔用。
  2. 不剝奪條件:即A 在佔用記錄1時候,其他線程是無法強制讓A 釋放資源的,必須等待
  3. 請求並保持條件:即A 在佔用記錄1時候,還去申請記錄2
  4. 循環等待條件:即A 在佔用了記錄1後,繼續申請記錄2,直到申請到記錄2爲止,否則一直申請

死鎖 好似 生活中的槓精,自己有老婆還要去找別人老婆,那別人怎麼能放過你,不把你打死。
死鎖就像婚姻,生活中的例子:
主演:黃腳明、癢穎baby、劉開胃、楊咪咪、王藕。
劇情:在一個月黑風高的夜晚,黃腳明、癢穎baby結婚了,劉開胃、楊咪咪結婚了。在經歷了生活的洗禮之後黃腳明和劉開胃兩個騷年同時看上了他們對方的老婆,由於他們兩個都護着自己老婆,導致別人想得也得不到,最終劉開胃放棄了,但是他看上了隔壁村的王藕,王藕還是個單身汗,所以沒到月黑風高的夜晚劉開胃就去找王藕談劇本。 

           搞黃色 原諒我的低俗了。。。。。。。。

3.死鎖實例

public class DeadLock implements Runnable{
    
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    private boolean flag;
    
    public DeadLock(boolean flag){
        this.flag = flag;
    }
    
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "運行");
        
        if(flag){
            synchronized(obj1){
                System.out.println(Thread.currentThread().getName() + "已經鎖住obj1");
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                synchronized(obj2){
                    // 執行不到這裏
                    System.out.println("1秒鐘後,"+Thread.currentThread().getName()
                                + "鎖住obj2");
                }
            }
        }else{
            synchronized(obj2){
                System.out.println(Thread.currentThread().getName() + "已經鎖住obj2");
                try {  
                    Thread.sleep(1000);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                synchronized(obj1){
                    // 執行不到這裏
                    System.out.println("1秒鐘後,"+Thread.currentThread().getName()
                                + "鎖住obj1");
                }
            }
        }
    }

}


package com.demo.test;

public class DeadLockTest {

     public static void main(String[] args) {
         
         Thread t1 = new Thread(new DeadLock(true), "線程1");
         Thread t2 = new Thread(new DeadLock(false), "線程2");

         t1.start();
         t2.start();
    }
}

運行結果:
線程1運行
線程1已經鎖住obj1
線程2運行
線程2已經鎖住obj2
摘學於:https://www.cnblogs.com/xiaoxi/p/8311034.html

一個簡單的死鎖類 t1先運行,這個時候flag==true,先鎖定obj1,然後睡眠1秒鐘而t1在睡眠的時候,另一個線程t2啓動,flag==false,先鎖定obj2,然後也睡眠1秒鐘t1睡眠結束後需要鎖定obj2才能繼續執行,而此時obj2已被t2鎖定t2睡眠結束後需要鎖定obj1才能繼續執行,而此時obj1已被t1鎖定t1、t2相互等待,都需要得到對方鎖定的資源才能繼續執行,從而死鎖。 

    從這個例子也可以反映出,死鎖是因爲多線程訪問共享資源,由於訪問的順序不當所造成的,通常是一個線程鎖定了一個資源A,而又想去鎖定資源B;在另一個線程中,鎖定了資源B,而又想去鎖定資源A以完成自身的操作,兩個線程都想得到對方的資源,而不願釋放自己的資源,造成兩個線程都在等待,而無法執行的情況。

4.死鎖解決方式

  1. 加鎖順序
    有A、B、C三個線程按照一定的順序一次獲取鎖a、b、c,因爲如果ABC必須從a開始獲取鎖然後才能獲取b,獲取c,只能先獲取a鎖的線程才能獲取b、c。讓線程以一定的順序拿鎖。但是這是要求在某種特定的場景,一般不適用。

  2. 加鎖時限
    在獲取鎖的時候,可以嘗試獲取鎖tryLock(timeout)/tryLock()加一個超時時限,如果超過了設置的時間,依然沒有得到鎖,如果自己已經佔有了一個鎖,那就釋放自己的鎖回退。這種方式針對ReentrantLock加鎖方式適用,對於synchronzied內部鎖是不適用的。

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

        每當一個線程獲得了鎖,會在線程和鎖相關的數據結構中(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自己持有着。這是它就知道發生了死鎖。

下一篇學習線程池

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