更新時(shí)間:2019-08-18 09:00:00 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽2926次
什么是死鎖?
死鎖是這樣一種情形:多個(gè)線程同時(shí)被阻塞,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放.由于線程被無(wú)限期地阻塞,因此程序不能正常運(yùn)行.形象的說(shuō)就是:一個(gè)寶藏需要兩把鑰匙來(lái)打開(kāi),同時(shí)間正好來(lái)了兩個(gè)人,他們一人一把鑰匙,但是雙方都再等著對(duì)方能交出鑰匙來(lái)打開(kāi)寶藏,誰(shuí)都沒(méi)釋放自己的那把鑰匙.就這樣這倆人一直僵持下去,直到開(kāi)發(fā)人員發(fā)現(xiàn)這個(gè)局面。
導(dǎo)致死鎖的根源在于不適當(dāng)?shù)剡\(yùn)用“synchronized”關(guān)鍵詞來(lái)管理線程對(duì)特定對(duì)象的訪問(wèn).“synchronized”關(guān)鍵詞的作用是,確保在某個(gè)時(shí)刻只有一個(gè)線程被允許執(zhí)行特定的代碼塊,因此,被允許執(zhí)行的線程首先必須擁有對(duì)變量或?qū)ο蟮呐潘栽L問(wèn)權(quán).當(dāng)線程訪問(wèn)對(duì)象時(shí),線程會(huì)給對(duì)象加鎖,而這個(gè)鎖導(dǎo)致其它也想訪問(wèn)同一對(duì)象的線程被阻塞,直至第一個(gè)線程釋放它加在對(duì)象上的鎖。
舉個(gè)例子
死鎖的產(chǎn)生大部分都是在你不知情的時(shí)候.我們通過(guò)一個(gè)例子來(lái)看下什么是死鎖。
1、synchronized嵌套
synchronized關(guān)鍵字可以保證多線程再訪問(wèn)到synchronized修飾的方法的時(shí)候保證了同步性.就是線程A訪問(wèn)到這個(gè)方法的時(shí)候線程B同時(shí)也來(lái)訪問(wèn)這個(gè)方法,這時(shí)線程B將進(jìn)行阻塞,等待線程A執(zhí)行完才可以去訪問(wèn).這里就要用到synchronized所持有的同步鎖.具體來(lái)看代碼:
/首先我們先定義兩個(gè)final的對(duì)象鎖.可以看做是共有的資源.
final Object lockA = new Object();
final Object lockB = new Object();
//生產(chǎn)者A
class ProductThreadA implements Runnable{
@Override
public void run() {
//這里一定要讓線程睡一會(huì)兒來(lái)模擬處理數(shù)據(jù) ,要不然的話死鎖的現(xiàn)象不會(huì)那么的明顯.這里就是同步語(yǔ)句塊里面,首先獲得對(duì)象鎖lockA,然后執(zhí)行一些代碼,隨后我們需要對(duì)象鎖lockB去執(zhí)行另外一些代碼.
synchronized (lockA){
//這里一個(gè)log日志
Log.e("CHAO","ThreadA lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
//這里一個(gè)log日志
Log.e("CHAO","ThreadA lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//生產(chǎn)者B
class ProductThreadB implements Runnable{
//我們生產(chǎn)的順序真好好生產(chǎn)者A相反,我們首先需要對(duì)象鎖lockB,然后需要對(duì)象鎖lockA.
@Override
public void run() {
synchronized (lockB){
//這里一個(gè)log日志
Log.e("CHAO","ThreadB lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA){
//這里一個(gè)log日志
Log.e("CHAO","ThreadB lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//這里運(yùn)行線程
ProductThreadA productThreadA = new ProductThreadA();
ProductThreadB productThreadB = new ProductThreadB();
Thread threadA = new Thread(productThreadA);
Thread threadB = new Thread(productThreadB);
threadA.start();
threadB.start();
分析一下,當(dāng)threadA開(kāi)始執(zhí)行run方法的時(shí)候,它會(huì)先持有對(duì)象鎖localA,然后睡眠2秒,這時(shí)候threadB也開(kāi)始執(zhí)行run方法,它持有的是localB對(duì)象鎖.當(dāng)threadA運(yùn)行到第二個(gè)同步方法的時(shí)候,發(fā)現(xiàn)localB的對(duì)象鎖不能使用(threadB未釋放localB鎖),threadA就停在這里等待localB鎖.隨后threadB也執(zhí)行到第二個(gè)同步方法,去訪localA對(duì)象鎖的時(shí)候發(fā)現(xiàn)localA還沒(méi)有被釋放(threadA未釋放localA鎖),threadB也停在這里等待localA鎖釋放.就這樣兩個(gè)線程都沒(méi)辦法繼續(xù)執(zhí)行下去,進(jìn)入死鎖的狀態(tài). 看下運(yùn)行結(jié)果:
10-20 14:54:39.940 18162-18178/? E/CHAO: ThreadA lock lockA
10-20 14:54:39.940 18162-18179/? E/CHAO: ThreadB lock lockB
當(dāng)不會(huì)死鎖的時(shí)候應(yīng)該是打印四條log的,這里明顯的出現(xiàn)了死鎖的現(xiàn)象。
死鎖出現(xiàn)的原因
當(dāng)我們了解在什么情況下會(huì)產(chǎn)生死鎖,以及什么是死鎖的時(shí)候,我們?cè)趯?xiě)代碼的時(shí)候應(yīng)該盡量的去避免這個(gè)誤區(qū).產(chǎn)生死鎖必須同時(shí)滿足以下四個(gè)條件,只要其中任一條件不成立,死鎖就不會(huì)發(fā)生。
互斥條件:線程要求對(duì)所分配的資源進(jìn)行排他性控制,即在一段時(shí)間內(nèi)某 資源僅為一個(gè)進(jìn)程所占有.此時(shí)若有其他進(jìn)程請(qǐng)求該資源.則請(qǐng)求進(jìn)程只能等待。
不剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能由獲得該資源的線程自己來(lái)釋放(只能是主動(dòng)釋放)。
請(qǐng)求和保持條件:線程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其他線程占有,此時(shí)請(qǐng)求線程被阻塞,但對(duì)自己已獲得的資源保持不放。
循環(huán)等待條件:存在一種線程資源的循環(huán)等待鏈,鏈中每一個(gè)線程已獲得的資源同時(shí)被鏈中下一個(gè)線程所請(qǐng)求。
解決死鎖的方法
1、解決死鎖主要方法如下:
(1)不考慮此問(wèn)題,樂(lè)觀的角度,鴕鳥(niǎo)算法
?。?)不讓死鎖發(fā)生:
?、偎梨i預(yù)防。
靜態(tài)策略,通過(guò)設(shè)計(jì)合適的資源分配算法,不讓死鎖發(fā)生
?、谒梨i避免
動(dòng)態(tài)策略,以不讓死鎖發(fā)生為目標(biāo),跟蹤并評(píng)估資源分配過(guò)程,根據(jù)評(píng)估結(jié)果決策是否分配
(3)讓死鎖發(fā)生:死鎖的檢測(cè)與解除
2.死鎖預(yù)防的具體方法:主要是破壞產(chǎn)生死鎖的四個(gè)必要條件中的任何一個(gè)條件。
?。?)破壞“互斥使用/資源獨(dú)占”條件
使用資源轉(zhuǎn)換技術(shù),將獨(dú)占資源變?yōu)楣蚕碣Y源
?。?)破壞“占有且等待”條件
?、俜桨?:每個(gè)進(jìn)程在運(yùn)行前必須一次性的申請(qǐng)它所要求的全部資源,且僅當(dāng)該進(jìn)程所要的資源均可滿足時(shí)才一次性的分配。
資源利用率低,“饑餓”現(xiàn)象
②在允許進(jìn)程動(dòng)態(tài)申請(qǐng)資源的前提下規(guī)定,一個(gè)進(jìn)行在申請(qǐng)新資源,且不能立即得到滿足,必須釋放已占有的全部資源。若需要再重新申請(qǐng)
(3)破壞“不可搶占”條件
可以通過(guò)操作系統(tǒng)搶占這一資源(根據(jù)進(jìn)程的不同優(yōu)先級(jí))
局限性:適用于狀態(tài)易于保存和恢復(fù)的資源,如CPU(搶占式的調(diào)度算法),內(nèi)存(頁(yè)面置換算法)
(4)破壞“循環(huán)等待”條件
通過(guò)定義資源類型的線性順序?qū)崿F(xiàn)
方案:資源有序分配法。(如哲學(xué)家就餐問(wèn)題)
也就是把資源中所有的資源編號(hào),進(jìn)程在申請(qǐng)資源時(shí),必須嚴(yán)格按照資源編號(hào)的遞增次序進(jìn)行,否則操作系統(tǒng)不予分配。(資源使用的頻繁性?)
3、死鎖避免的方法

?。?)對(duì)不同分區(qū)進(jìn)行不同的處理,左下可同時(shí)分配資源;右下區(qū)域可能會(huì)產(chǎn)生死鎖,應(yīng)該先分配P,釋放后再分配Q;左上同理;右上區(qū)域時(shí)會(huì)產(chǎn)生死鎖現(xiàn)象,不予分配資源。對(duì)可能發(fā)生死鎖和可能產(chǎn)生死鎖的不予分配資源,否則(安全狀態(tài))分配資源。
?。?)安全狀態(tài)是指每個(gè)進(jìn)程Pi以后還需要的資源量不超過(guò)系統(tǒng)當(dāng)前剩余資源量與所有進(jìn)程Pj當(dāng)前占有資源量之和。
4、死鎖的檢測(cè)與解除
(1)允許死鎖發(fā)生,但是操作系統(tǒng)會(huì)不斷監(jiān)視系統(tǒng)進(jìn)展情況,判斷死鎖是否真的發(fā)生。(進(jìn)程等待時(shí)檢測(cè),定時(shí)檢測(cè),系統(tǒng)資源利用率下降)
(2)一旦死鎖發(fā)生,采用專門(mén)的措施,解除死鎖并以最小的代價(jià)恢復(fù)系統(tǒng)運(yùn)行。
以上就是動(dòng)力節(jié)點(diǎn)java學(xué)院小編介紹的“Java多線程之死鎖的出現(xiàn)和解決方法”的相關(guān)內(nèi)容,希望對(duì)大家有幫助,更多精彩內(nèi)容請(qǐng)關(guān)注動(dòng)力節(jié)點(diǎn)java學(xué)院官網(wǎng)。
相關(guān)閱讀
Java實(shí)驗(yàn)班
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
Java就業(yè)班
有基礎(chǔ) 直達(dá)就業(yè)
Java夜校直播班
業(yè)余時(shí)間 高薪轉(zhuǎn)行
Java在職加薪班
工作1~3年,加薪神器
Java架構(gòu)師班
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)