摘要:此時(shí)線程需要鎖才能繼續(xù)往下執(zhí)行。但是線程的鎖并沒(méi)有釋放,線程的鎖也沒(méi)有釋放。
前言
只有光頭才能變強(qiáng)
回顧前面:
ThreadLocal就是這么簡(jiǎn)單
多線程三分鐘就可以入個(gè)門(mén)了!
多線程基礎(chǔ)必要知識(shí)點(diǎn)!看了學(xué)習(xí)多線程事半功倍
Java鎖機(jī)制了解一下
AQS簡(jiǎn)簡(jiǎn)單單過(guò)一遍
Lock鎖子類(lèi)了解一下
線程池你真不來(lái)了解一下嗎?
本篇主要是講解死鎖,這是我在多線程的最后一篇了。主要將多線程的基礎(chǔ)過(guò)一遍,以后有機(jī)會(huì)再繼續(xù)深入!
死鎖是在多線程中也是比較重要的知識(shí)點(diǎn)了!
那么接下來(lái)就開(kāi)始吧,如果文章有錯(cuò)誤的地方請(qǐng)大家多多包涵,不吝在評(píng)論區(qū)指正哦~
聲明:本文使用JDK1.8一、死鎖講解
在Java中使用多線程,就會(huì)有可能導(dǎo)致死鎖問(wèn)題。死鎖會(huì)讓對(duì)應(yīng)產(chǎn)生死鎖的線程卡住,不再程序往下執(zhí)行。我們只能通過(guò)中止并重啟的方式來(lái)讓程序重新執(zhí)行。
這是我們非常不愿意看到的一種現(xiàn)象,我們要盡可能避免死鎖的情況發(fā)生!
造成死鎖的原因可以概括成三句話:
當(dāng)前線程擁有其他線程需要的資源
當(dāng)前線程等待其他線程已擁有的資源
都不放棄自己擁有的資源
1.1鎖順序死鎖首先我們來(lái)看一下最簡(jiǎn)單的死鎖(鎖順序死鎖)是怎么樣發(fā)生的:
public class LeftRightDeadlock { private final Object left = new Object(); private final Object right = new Object(); public void leftRight() { // 得到left鎖 synchronized (left) { // 得到right鎖 synchronized (right) { doSomething(); } } } public void rightLeft() { // 得到right鎖 synchronized (right) { // 得到left鎖 synchronized (left) { doSomethingElse(); } } } }
我們的線程是交錯(cuò)執(zhí)行的,那么就很有可能出現(xiàn)以下的情況:
線程A調(diào)用leftRight()方法,得到left鎖
同時(shí)線程B調(diào)用rightLeft()方法,得到right鎖
線程A和線程B都繼續(xù)執(zhí)行,此時(shí)線程A需要right鎖才能繼續(xù)往下執(zhí)行。此時(shí)線程B需要left鎖才能繼續(xù)往下執(zhí)行。
但是:線程A的left鎖并沒(méi)有釋放,線程B的right鎖也沒(méi)有釋放。
所以他們都只能等待,而這種等待是無(wú)期限的-->永久等待-->死鎖
1.2動(dòng)態(tài)鎖順序死鎖我們看一下下面的例子,你認(rèn)為會(huì)發(fā)生死鎖嗎?
// 轉(zhuǎn)賬 public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { // 鎖定匯賬賬戶 synchronized (fromAccount) { // 鎖定來(lái)賬賬戶 synchronized (toAccount) { // 判余額是否大于0 if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException(); } else { // 匯賬賬戶減錢(qián) fromAccount.debit(amount); // 來(lái)賬賬戶增錢(qián) toAccount.credit(amount); } } } }
上面的代碼看起來(lái)是沒(méi)有問(wèn)題的:鎖定兩個(gè)賬戶來(lái)判斷余額是否充足才進(jìn)行轉(zhuǎn)賬!
但是,同樣有可能會(huì)發(fā)生死鎖:
如果兩個(gè)線程同時(shí)調(diào)用transferMoney()
線程A從X賬戶向Y賬戶轉(zhuǎn)賬
線程B從賬戶Y向賬戶X轉(zhuǎn)賬
那么就會(huì)發(fā)生死鎖。
A:transferMoney(myAccount,yourAccount,10); B:transferMoney(yourAccount,myAccount,20);1.3協(xié)作對(duì)象之間發(fā)生死鎖
我們來(lái)看一下下面的例子:
public class CooperatingDeadlock { // Warning: deadlock-prone! class Taxi { @GuardedBy("this") private Point location, destination; private final Dispatcher dispatcher; public Taxi(Dispatcher dispatcher) { this.dispatcher = dispatcher; } public synchronized Point getLocation() { return location; } // setLocation 需要Taxi內(nèi)置鎖 public synchronized void setLocation(Point location) { this.location = location; if (location.equals(destination)) // 調(diào)用notifyAvailable()需要Dispatcher內(nèi)置鎖 dispatcher.notifyAvailable(this); } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } class Dispatcher { @GuardedBy("this") private final Settaxis; @GuardedBy("this") private final Set availableTaxis; public Dispatcher() { taxis = new HashSet (); availableTaxis = new HashSet (); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.add(taxi); } // 調(diào)用getImage()需要Dispatcher內(nèi)置鎖 public synchronized Image getImage() { Image image = new Image(); for (Taxi t : taxis) // 調(diào)用getLocation()需要Taxi內(nèi)置鎖 image.drawMarker(t.getLocation()); return image; } } class Image { public void drawMarker(Point p) { } } }
上面的getImage()和setLocation(Point location)都需要獲取兩個(gè)鎖的
并且在操作途中是沒(méi)有釋放鎖的
這就是隱式獲取兩個(gè)鎖(對(duì)象之間協(xié)作)..
這種方式也很容易就造成死鎖.....
二、避免死鎖的方法避免死鎖可以概括成三種方法:
固定加鎖的順序(針對(duì)鎖順序死鎖)
開(kāi)放調(diào)用(針對(duì)對(duì)象之間協(xié)作造成的死鎖)
使用定時(shí)鎖-->tryLock()
如果等待獲取鎖時(shí)間超時(shí),則拋出異常而不是一直等待!
2.1固定鎖順序避免死鎖上面transferMoney()發(fā)生死鎖的原因是因?yàn)?strong>加鎖順序不一致而出現(xiàn)的~
正如書(shū)上所說(shuō)的:如果所有線程以固定的順序來(lái)獲得鎖,那么程序中就不會(huì)出現(xiàn)鎖順序死鎖問(wèn)題!
那么上面的例子我們就可以改造成這樣子:
public class InduceLockOrder { // 額外的鎖、避免兩個(gè)對(duì)象hash值相等的情況(即使很少) private static final Object tieLock = new Object(); public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount) throws InsufficientFundsException { class Helper { public void transfer() throws InsufficientFundsException { if (fromAcct.getBalance().compareTo(amount) < 0) throw new InsufficientFundsException(); else { fromAcct.debit(amount); toAcct.credit(amount); } } } // 得到鎖的hash值 int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); // 根據(jù)hash值來(lái)上鎖 if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) {// 根據(jù)hash值來(lái)上鎖 synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else {// 額外的鎖、避免兩個(gè)對(duì)象hash值相等的情況(即使很少) synchronized (tieLock) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } } } }
得到對(duì)應(yīng)的hash值來(lái)固定加鎖的順序,這樣我們就不會(huì)發(fā)生死鎖的問(wèn)題了!
2.2開(kāi)放調(diào)用避免死鎖在協(xié)作對(duì)象之間發(fā)生死鎖的例子中,主要是因?yàn)樵?strong>調(diào)用某個(gè)方法時(shí)就需要持有鎖,并且在方法內(nèi)部也調(diào)用了其他帶鎖的方法!
如果在調(diào)用某個(gè)方法時(shí)不需要持有鎖,那么這種調(diào)用被稱(chēng)為開(kāi)放調(diào)用!
我們可以這樣來(lái)改造:
同步代碼塊最好僅被用于保護(hù)那些涉及共享狀態(tài)的操作!
class CooperatingNoDeadlock { @ThreadSafe class Taxi { @GuardedBy("this") 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) { boolean reachedDestination; // 加Taxi內(nèi)置鎖 synchronized (this) { this.location = location; reachedDestination = location.equals(destination); } // 執(zhí)行同步代碼塊后完畢,釋放鎖 if (reachedDestination) // 加Dispatcher內(nèi)置鎖 dispatcher.notifyAvailable(this); } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } @ThreadSafe class Dispatcher { @GuardedBy("this") private final Settaxis; @GuardedBy("this") private final Set availableTaxis; public Dispatcher() { taxis = new HashSet (); availableTaxis = new HashSet (); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.add(taxi); } public Image getImage() { Set copy; // Dispatcher內(nèi)置鎖 synchronized (this) { copy = new HashSet (taxis); } // 執(zhí)行同步代碼塊后完畢,釋放鎖 Image image = new Image(); for (Taxi t : copy) // 加Taix內(nèi)置鎖 image.drawMarker(t.getLocation()); return image; } } class Image { public void drawMarker(Point p) { } } }
使用開(kāi)放調(diào)用是非常好的一種方式,應(yīng)該盡量使用它~
2.3使用定時(shí)鎖使用顯式Lock鎖,在獲取鎖時(shí)使用tryLock()方法。當(dāng)?shù)却?strong>超過(guò)時(shí)限的時(shí)候,tryLock()不會(huì)一直等待,而是返回錯(cuò)誤信息。
使用tryLock()能夠有效避免死鎖問(wèn)題~~
2.4死鎖檢測(cè)雖然造成死鎖的原因是因?yàn)槲覀冊(cè)O(shè)計(jì)得不夠好,但是可能寫(xiě)代碼的時(shí)候不知道哪里發(fā)生了死鎖。
JDK提供了兩種方式來(lái)給我們檢測(cè):
JconsoleJDK自帶的圖形化界面工具,使用JDK給我們的的工具JConsole
Jstack是JDK自帶的命令行工具,主要用于線程Dump分析。
具體可參考:
https://www.cnblogs.com/flyingeagle/articles/6853167.html
三、總結(jié)發(fā)生死鎖的原因主要由于:
線程之間交錯(cuò)執(zhí)行
解決:以固定的順序加鎖
執(zhí)行某方法時(shí)就需要持有鎖,且不釋放
解決:縮減同步代碼塊范圍,最好僅操作共享變量時(shí)才加鎖
永久等待
解決:使用tryLock()定時(shí)鎖,超過(guò)時(shí)限則返回錯(cuò)誤信息
在操作系統(tǒng)層面上看待死鎖問(wèn)題(這是我之前做的筆記、很淺顯):
操作系統(tǒng)第五篇【死鎖】
參考資料:
《Java核心技術(shù)卷一》
《Java并發(fā)編程實(shí)戰(zhàn)》
《計(jì)算機(jī)操作系統(tǒng) 湯小丹》
如果文章有錯(cuò)的地方歡迎指正,大家互相交流。習(xí)慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號(hào):Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支持了!希望能多介紹給其他有需要的朋友
文章的目錄導(dǎo)航:
https://zhongfucheng.bitcron.com/post/shou-ji/wen-zhang-dao-hang
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71107.html
摘要:超詳細(xì)的面試題總結(jié)一之基本知識(shí)多線程和虛擬機(jī)創(chuàng)建線程有幾種不同的方式你喜歡哪一種為什么繼承類(lèi)實(shí)現(xiàn)接口應(yīng)用程序可以使用框架來(lái)創(chuàng)建線程池實(shí)現(xiàn)接口。死亡線程方法執(zhí)行結(jié)束,或者因異常退出了方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。 超詳細(xì)的Java面試題總結(jié)(一)之Java基本知識(shí) 多線程和Java虛擬機(jī) 創(chuàng)建線程有幾種不同的方式?你喜歡哪一種?為什么? 繼承Thread類(lèi) 實(shí)現(xiàn)R...
摘要:在這個(gè)范圍廣大的并發(fā)技術(shù)領(lǐng)域當(dāng)中多線程編程可以說(shuō)是基礎(chǔ)和核心,大多數(shù)抽象并發(fā)問(wèn)題的構(gòu)思與解決都是基于多線程模型來(lái)進(jìn)行的。一般來(lái)說(shuō),多線程程序會(huì)面臨三類(lèi)問(wèn)題正確性問(wèn)題效率問(wèn)題死鎖問(wèn)題。 多線程編程或者說(shuō)范圍更大的并發(fā)編程是一種非常復(fù)雜且容易出錯(cuò)的編程方式,但是我們?yōu)槭裁催€要冒著風(fēng)險(xiǎn)艱辛地學(xué)習(xí)各種多線程編程技術(shù)、解決各種并發(fā)問(wèn)題呢? 因?yàn)椴l(fā)是整個(gè)分布式集群的基礎(chǔ),通過(guò)分布式集群不僅可以大...
摘要:線程間通信其實(shí)就是多個(gè)線程操作同一個(gè)資源,但動(dòng)作不同。同步前提是多線程。將該線程載入線程池,等待喚醒。該方法拋出異常,故需要配合使用隨機(jī)喚醒線程池中一線程。線程為了檢測(cè)死鎖,它需要遞進(jìn)地檢測(cè)所有被請(qǐng)求的鎖。 線程間通信 其實(shí)就是多個(gè)線程操作同一個(gè)資源,但動(dòng)作不同。示例:在某個(gè)數(shù)據(jù)庫(kù)中,Input輸入人的姓名,性別,Output輸出,兩個(gè)線程同時(shí)作用。思考:1.明確哪些代碼是多線程操作的...
摘要:雖然本文是一篇介紹死鎖及其解決方式的文章,但是對(duì)于多線程程序中的非死鎖問(wèn)題我們也應(yīng)該有所了解,這樣才能寫(xiě)出正確且高效的多線程程序。 死鎖是多線程編程或者說(shuō)是并發(fā)編程中的一個(gè)經(jīng)典問(wèn)題,也是我們?cè)趯?shí)際工作中很可能會(huì)碰到的問(wèn)題。相信大部分讀者對(duì)死鎖這個(gè)詞都是略有耳聞的,但從我對(duì)后端開(kāi)發(fā)崗位的面試情況來(lái)看很多同學(xué)往往對(duì)死鎖都還沒(méi)有系統(tǒng)的了解。雖然死鎖聽(tīng)起來(lái)很高深,但是實(shí)際上已經(jīng)被研究得比較透徹...
摘要:本人郵箱歡迎轉(zhuǎn)載轉(zhuǎn)載請(qǐng)注明網(wǎng)址代碼已經(jīng)全部托管有需要的同學(xué)自行下載引言多線程如果設(shè)計(jì)的不合理的話很可能就會(huì)出現(xiàn)死鎖當(dāng)兩個(gè)或者多個(gè)線程同事想要去獲取共享資源的鎖時(shí)但每個(gè)線程都要等其他線程把他們各自的鎖給釋放才能繼續(xù)運(yùn)行這就是死鎖出現(xiàn)死鎖必須具 本人郵箱: 歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明網(wǎng)址 http://blog.csdn.net/tianshi_kcogithub: https://github...
閱讀 3940·2021-10-12 10:12
閱讀 2899·2021-09-10 11:18
閱讀 3684·2019-08-30 15:54
閱讀 2816·2019-08-30 15:53
閱讀 651·2019-08-30 13:54
閱讀 977·2019-08-30 13:21
閱讀 2270·2019-08-30 12:57
閱讀 1700·2019-08-30 11:10