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

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

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

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

  1. 互斥條件:一個資源每次只能被一個線程使用扁耐。
  2. 請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放产阱。
  3. 不剝奪條件:線程已獲得的資源婉称,在未使用完之前,不能強行剝奪。
  4. 循環(huán)等待條件:若干線程之間形成一種頭尾相接的循環(huán)等待資源關系王暗。

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

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

a和b兩個方法都需要獲得A鎖和B鎖策肝。一個線程執(zhí)行a方法且已經(jīng)獲得了A鎖,在等待B鎖隐绵;另一個線程執(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)的鎖順序死鎖的方法就是:所有需要多個鎖的線程,都要以相同的順序來獲得鎖峭跳。 **

//正確的代碼  
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");  
            }  
        }  
    }  
}  

三膘婶、動態(tài)的鎖順序死鎖:

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

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

**動態(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é)作對象之間發(fā)生的死鎖:

有時帚桩,死鎖并不會那么明顯亿驾,比如兩個相互協(xié)作的類之間的死鎖,比如下面的代碼:一個線程調(diào)用了Taxi對象的setLocation方法朗儒,另一個線程調(diào)用了Dispatcher對象的getImage方法颊乘。此時可能會發(fā)生,第一個線程持有Taxi對象鎖并等待Dispatcher對象鎖醉锄,另一個線程持有Dispatcher對象鎖并等待Taxi對象鎖乏悄。

//可能發(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對象鎖  
    }  
}  
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對象鎖  
        return image;  
    }  
}  

上面的代碼中檩小,我們在持有鎖的情況下調(diào)用了外部的方法,這是非常危險的(可能發(fā)生死鎖)烟勋。為了避免這種危險的情況發(fā)生规求,我們使用開放調(diào)用筐付。如果調(diào)用某個外部方法時不需要持有鎖,我們稱之為開放調(diào)用阻肿。

**解決協(xié)作對象之間發(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)的鎖順序死鎖赴邻,動態(tài)的鎖順序死鎖印衔,協(xié)作對象之間的死鎖。在寫代碼時姥敛,要確保線程在獲取多個鎖時采用一致的順序奸焙。同時,要避免在持有鎖的情況下調(diào)用外部方法彤敛。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末与帆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子墨榄,更是在濱河造成了極大的恐慌鲤桥,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渠概,死亡現(xiàn)場離奇詭異茶凳,居然都是意外死亡,警方通過查閱死者的電腦和手機播揪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門贮喧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猪狈,你說我怎么就攤上這事箱沦。” “怎么了雇庙?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵谓形,是天一觀的道長。 經(jīng)常有香客問我疆前,道長寒跳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任竹椒,我火速辦了婚禮童太,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己书释,他們只是感情好翘贮,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爆惧,像睡著了一般狸页。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扯再,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天肴捉,我揣著相機與錄音,去河邊找鬼叔收。 笑死,一個胖子當著我的面吹牛傲隶,可吹牛的內(nèi)容都是我干的饺律。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼跺株,長吁一口氣:“原來是場噩夢啊……” “哼复濒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乒省,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤巧颈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后袖扛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砸泛,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年蛆封,在試婚紗的時候發(fā)現(xiàn)自己被綠了唇礁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡惨篱,死狀恐怖盏筐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砸讳,我是刑警寧澤琢融,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站簿寂,受9級特大地震影響漾抬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜常遂,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一奋蔚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦泊碑、人聲如沸坤按。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽臭脓。三九已至,卻和暖如春腹忽,著一層夾襖步出監(jiān)牢的瞬間来累,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工窘奏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘹锁,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓着裹,卻偏偏與公主長得像领猾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子骇扇,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

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

  • 1. Java基礎部分 基礎部分的順序:基本語法摔竿,類相關的語法,內(nèi)部類的語法少孝,繼承相關的語法继低,異常的語法,線程的語...
    子非魚_t_閱讀 31,664評論 18 399
  • 一.線程安全性 線程安全是建立在對于對象狀態(tài)訪問操作進行管理稍走,特別是對共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 852評論 0 3
  • Java8張圖 11袁翁、字符串不變性 12、equals()方法婿脸、hashCode()方法的區(qū)別 13梦裂、...
    Miley_MOJIE閱讀 3,709評論 0 11
  • 從三月份找實習到現(xiàn)在,面了一些公司盖淡,掛了不少年柠,但最終還是拿到小米、百度褪迟、阿里冗恨、京東、新浪味赃、CVTE掀抹、樂視家的研發(fā)崗...
    時芥藍閱讀 42,278評論 11 349
  • Java并發(fā)總結(jié) 1.多線程的優(yōu)點 資源利用率更好 程序在某些情況下更簡單 程序響應更快 2.創(chuàng)建線程 1.實現(xiàn)R...
    不會上樹的猴子閱讀 1,033評論 0 5