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

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

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

一般來(lái)說(shuō),要出現(xiàn)死鎖問(wèn)題需要滿(mǎn)足以下條件:

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

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

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

a和b兩個(gè)方法都需要獲得A鎖和B鎖。一個(gè)線(xiàn)程執(zhí)行a方法且已經(jīng)獲得了A鎖,在等待B鎖;另一個(gè)線(xiàn)程執(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è)鎖的線(xiàn)程,都要以相同的順序來(lá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 (lockA) {  
            synchronized (lockB) {  
                System.out.println("function b");  
            }  
        }  
    }  
}  

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

動(dòng)態(tài)的鎖順序死鎖是指兩個(gè)線(xiàn)程調(diào)用同一個(gè)方法時(shí),傳入的參數(shù)顛倒造成的死鎖。如下代碼,一個(gè)線(xiàn)程調(diào)用了transferMoney方法并傳入?yún)?shù)accountA,accountB;另一個(gè)線(xiàn)程調(diào)用了transferMoney方法并傳入?yún)?shù)accountB,accountA。此時(shí)就可能發(fā)生在靜態(tài)的鎖順序死鎖中存在的問(wèn)題,即:第一個(gè)線(xiàn)程獲得了accountA鎖并等待accountB鎖,第二個(gè)線(xiàn)程獲得了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來(lái)定義鎖的順序。確保所有的線(xiàn)程都以相同的順序獲得鎖 **

//正確的代碼  
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é)作的類(lèi)之間的死鎖,比如下面的代碼:一個(gè)線(xiàn)程調(diào)用了Taxi對(duì)象的setLocation方法,另一個(gè)線(xiàn)程調(diào)用了Dispatcher對(duì)象的getImage方法。此時(shí)可能會(huì)發(fā)生,第一個(gè)線(xiàn)程持有Taxi對(duì)象鎖并等待Dispatcher對(duì)象鎖,另一個(gè)線(xiàn)程持有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ā)生,我們使用開(kāi)放調(diào)用。如果調(diào)用某個(gè)外部方法時(shí)不需要持有鎖,我們稱(chēng)之為開(kāi)放調(diào)用。

**解決協(xié)作對(duì)象之間發(fā)生的死鎖:需要使用開(kāi)放調(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);//使用開(kāi)放調(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());//使用開(kāi)放調(diào)用  
        return image;  
    }  
}  

五、總結(jié)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容