Java并發(fā)——三種典型的死鎖

在JAVA并發(fā)編程中,我們使用鎖來確保可變共享變量的安全性。要注意的是,不正確的使用鎖很容易導(dǎo)致死鎖。本篇文章轉(zhuǎn)載自:JAVA并發(fā)-3種典型的死鎖

一、死鎖產(chǎn)生的條件

一般來說,要出現(xiàn)死鎖問題需要滿足以下條件:

  1. 互斥條件:一個(gè)資源每次只能被一個(gè)線程使用。
  2. 請(qǐng)求與保持條件:一個(gè)線程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
  3. 不剝奪條件:線程已獲得的資源,在未使用完之前,不能強(qiáng)行剝奪。
  4. 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。

在JAVA編程中,有3種典型的死鎖類型:
靜態(tài)的鎖順序死鎖,動(dòng)態(tài)的鎖順序死鎖,協(xié)作對(duì)象之間發(fā)生的死鎖。

二、靜態(tài)的鎖順序死鎖

a和b兩個(gè)方法都需要獲得A鎖和B鎖。一個(gè)線程執(zhí)行a方法且已經(jīng)獲得了A鎖,在等待B鎖;另一個(gè)線程執(zhí)行了b方法且已經(jīng)獲得了B鎖,在等待A鎖。這種狀態(tài),就是發(fā)生了靜態(tài)的鎖順序死鎖。

//可能發(fā)生靜態(tài)鎖順序死鎖的代碼  
class StaticLockOrderDeadLock{  
    private final Object lockA=new Object();  
    private final Object lockB=new Object();  
    public void a(){  
        synchronized (lockA) {  
            synchronized (lockB) {  
                System.out.println("function a");  
            }  
        }  
    }  
      
    public void b(){  
        synchronized (lockB) {  
            synchronized (lockA) {  
                System.out.println("function b");  
            }  
        }  
    }  
}  

**解決靜態(tài)的鎖順序死鎖的方法就是:所有需要多個(gè)鎖的線程,都要以相同的順序來獲得鎖。 **

//正確的代碼  
class StaticLockOrderDeadLock{  
    private final Object lockA=new Object();  
    private final Object lockB=new Object();  
    public void a(){  
        synchronized (lockA) {  
            synchronized (lockB) {  
                System.out.println("function a");  
            }  
        }  
    }  
      
    public void b(){  
        synchronized (lockA) {  
            synchronized (lockB) {  
                System.out.println("function b");  
            }  
        }  
    }  
}  

三、動(dòng)態(tài)的鎖順序死鎖:

動(dòng)態(tài)的鎖順序死鎖是指兩個(gè)線程調(diào)用同一個(gè)方法時(shí),傳入的參數(shù)顛倒造成的死鎖。如下代碼,一個(gè)線程調(diào)用了transferMoney方法并傳入?yún)?shù)accountA,accountB;另一個(gè)線程調(diào)用了transferMoney方法并傳入?yún)?shù)accountB,accountA。此時(shí)就可能發(fā)生在靜態(tài)的鎖順序死鎖中存在的問題,即:第一個(gè)線程獲得了accountA鎖并等待accountB鎖,第二個(gè)線程獲得了accountB鎖并等待accountA鎖。

//可能發(fā)生動(dòng)態(tài)鎖順序死鎖的代碼  
class DynamicLockOrderDeadLock{  
    public void transefMoney(Account fromAccount,Account toAccount,Double amount){  
        synchronized (fromAccount) {  
            synchronized (toAccount) {  
                //...  
                fromAccount.minus(amount);  
                toAccount.add(amount);  
                //...  
            }  
        }  
    }  
}  

**動(dòng)態(tài)的鎖順序死鎖解決方案如下:使用System.identifyHashCode來定義鎖的順序。確保所有的線程都以相同的順序獲得鎖 **

//正確的代碼  
class DynamicLockOrderDeadLock{  
    private final Object myLock=new Object();  
    public void transefMoney(final Account fromAccount,final Account toAccount,final Double amount){  
        class Helper{  
            public void transfer(){  
                //...  
                fromAccount.minus(amount);  
                toAccount.add(amount);  
                //...  
            }  
        }  
        int  fromHash=System.identityHashCode(fromAccount);  
        int  toHash=System.identityHashCode(toAccount);  
          
        if(fromHash<toHash){  
            synchronized (fromAccount) {  
                synchronized (toAccount) {  
                    new Helper().transfer();  
                }  
            }  
        }else if(fromHash>toHash){  
            synchronized (toAccount) {  
                synchronized (fromAccount) {  
                    new Helper().transfer();  
                }  
            }  
        }else{  
            synchronized (myLock) {  
                synchronized (fromAccount) {  
                    synchronized (toAccount) {  
                        new Helper().transfer();  
                    }  
                }  
            }  
        }  
          
    }  
}  

四、協(xié)作對(duì)象之間發(fā)生的死鎖:

有時(shí),死鎖并不會(huì)那么明顯,比如兩個(gè)相互協(xié)作的類之間的死鎖,比如下面的代碼:一個(gè)線程調(diào)用了Taxi對(duì)象的setLocation方法,另一個(gè)線程調(diào)用了Dispatcher對(duì)象的getImage方法。此時(shí)可能會(huì)發(fā)生,第一個(gè)線程持有Taxi對(duì)象鎖并等待Dispatcher對(duì)象鎖,另一個(gè)線程持有Dispatcher對(duì)象鎖并等待Taxi對(duì)象鎖。

//可能發(fā)生死鎖  
class Taxi{  
    private Point location,destination;  
    private final Dispatcher dispatcher;  
    public Taxi(Dispatcher dispatcher) {  
        this.dispatcher=dispatcher;  
    }  
    public synchronized Point getLocation(){  
        return location;  
    }  
    public synchronized void setLocation(Point location){  
        this.location=location;  
        if(location.equals(destination))  
            dispatcher.notifyAvailable(this);//外部調(diào)用方法,可能等待Dispatcher對(duì)象鎖  
    }  
}  
class Dispatcher{  
    private final Set<Taxi> taxis;  
    private final Set<Taxi> availableTaxis;  
    public Dispatcher(){  
        taxis=new HashSet<Taxi>();  
        availableTaxis=new HashSet<Taxi>();  
    }  
    public synchronized void notifyAvailable(Taxi taxi){  
        availableTaxis.add(taxi);  
    }  
    public synchronized Image getImage(){  
        Image image=new Image();  
        for(Taxi t:taxis)  
            image.drawMarker(t.getLocation());//外部調(diào)用方法,可能等待Taxi對(duì)象鎖  
        return image;  
    }  
}  

上面的代碼中,我們?cè)诔钟墟i的情況下調(diào)用了外部的方法,這是非常危險(xiǎn)的(可能發(fā)生死鎖)。為了避免這種危險(xiǎn)的情況發(fā)生,我們使用開放調(diào)用。如果調(diào)用某個(gè)外部方法時(shí)不需要持有鎖,我們稱之為開放調(diào)用。

**解決協(xié)作對(duì)象之間發(fā)生的死鎖:需要使用開放調(diào)用,即避免在持有鎖的情況下調(diào)用外部的方法。 **

//正確的代碼  
class Taxi{  
    private Point location,destination;  
    private final Dispatcher dispatcher;  
    public Taxi(Dispatcher dispatcher) {  
        this.dispatcher=dispatcher;  
    }  
    public synchronized Point getLocation(){  
        return location;  
    }  
    public void setLocation(Point location){  
        boolean flag=false;  
        synchronized (this) {  
            this.location=location;  
            flag=location.equals(destination);            
        }  
        if(flag)  
            dispatcher.notifyAvailable(this);//使用開放調(diào)用  
    }  
}  
class Dispatcher{  
    private final Set<Taxi> taxis;  
    private final Set<Taxi> availableTaxis;  
    public Dispatcher(){  
        taxis=new HashSet<Taxi>();  
        availableTaxis=new HashSet<Taxi>();  
    }  
    public synchronized void notifyAvailable(Taxi taxi){  
        availableTaxis.add(taxi);  
    }  
    public Image getImage(){  
        Set<Taxi> copy;  
        synchronized (this) {  
            copy=new HashSet<Taxi>(taxis);  
        }  
        Image image=new Image();  
        for(Taxi t:copy)  
            image.drawMarker(t.getLocation());//使用開放調(diào)用  
        return image;  
    }  
}  

五、總結(jié)

綜上,是常見的3種死鎖的類型。即:靜態(tài)的鎖順序死鎖,動(dòng)態(tài)的鎖順序死鎖,協(xié)作對(duì)象之間的死鎖。在寫代碼時(shí),要確保線程在獲取多個(gè)鎖時(shí)采用一致的順序。同時(shí),要避免在持有鎖的情況下調(diào)用外部方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 31,769評(píng)論 18 399
  • 一.線程安全性 線程安全是建立在對(duì)于對(duì)象狀態(tài)訪問操作進(jìn)行管理,特別是對(duì)共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 876評(píng)論 0 3
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,732評(píng)論 0 11
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,373評(píng)論 11 349
  • Java并發(fā)總結(jié) 1.多線程的優(yōu)點(diǎn) 資源利用率更好 程序在某些情況下更簡(jiǎn)單 程序響應(yīng)更快 2.創(chuàng)建線程 1.實(shí)現(xiàn)R...
    不會(huì)上樹的猴子閱讀 1,041評(píng)論 0 5