Java多線程之死鎖的出現(xiàn)和解決方法
什么是死鎖?
死鎖是這樣一種情形:多個線程同時(shí)被阻塞,它們中的一個或者全部都在等待某個資源被釋放.由于線程被無限期地阻塞,因此程序不能正常運(yùn)行.形象的說就是:一個寶藏需要兩把鑰匙來打開,同時(shí)間正好來了兩個人,他們一人一把鑰匙,但是雙方都再等著對方能交出鑰匙來打開寶藏,誰都沒釋放自己的那把鑰匙.就這樣這倆人一直僵持下去,直到開發(fā)人員發(fā)現(xiàn)這個局面.
導(dǎo)致死鎖的根源在于不適當(dāng)?shù)剡\(yùn)用“synchronized”關(guān)鍵詞來管理線程對特定對象的訪問.“synchronized”關(guān)鍵詞的作用是,確保在某個時(shí)刻只有一個線程被允許執(zhí)行特定的代碼塊,因此,被允許執(zhí)行的線程首先必須擁有對變量或?qū)ο蟮呐潘栽L問權(quán).當(dāng)線程訪問對象時(shí),線程會給對象加鎖,而這個鎖導(dǎo)致其它也想訪問同一對象的線程被阻塞,直至第一個線程釋放它加在對象上的鎖.
對synchronized不太了解的話請點(diǎn)擊這里
舉個例子
死鎖的產(chǎn)生大部分都是在你不知情的時(shí)候.我們通過一個例子來看下什么是死鎖.
1.synchronized嵌套.
synchronized關(guān)鍵字可以保證多線程再訪問到synchronized修飾的方法的時(shí)候保證了同步性.就是線程A訪問到這個方法的時(shí)候線程B同時(shí)也來訪問這個方法,這時(shí)線程B將進(jìn)行阻塞,等待線程A執(zhí)行完才可以去訪問.這里就要用到synchronized所持有的同步鎖.具體來看代碼:
//首先我們先定義兩個final的對象鎖.可以看做是共有的資源.
final Object lockA = new Object();
final Object lockB = new Object();
//生產(chǎn)者A
class ProductThreadA implements Runnable{
@Override
public void run() {
//這里一定要讓線程睡一會兒來模擬處理數(shù)據(jù) ,要不然的話死鎖的現(xiàn)象不會那么的明顯.這里就是同步語句塊里面,首先獲得對象鎖lockA,然后執(zhí)行一些代碼,隨后我們需要對象鎖lockB去執(zhí)行另外一些代碼.
synchronized (lockA){
//這里一個log日志
Log.e("CHAO","ThreadA lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
//這里一個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相反,我們首先需要對象鎖lockB,然后需要對象鎖lockA.
@Override
public void run() {
synchronized (lockB){
//這里一個log日志
Log.e("CHAO","ThreadB lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA){
//這里一個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開始執(zhí)行run方法的時(shí)候,它會先持有對象鎖localA,然后睡眠2秒,這時(shí)候threadB也開始執(zhí)行run方法,它持有的是localB對象鎖.當(dāng)threadA運(yùn)行到第二個同步方法的時(shí)候,發(fā)現(xiàn)localB的對象鎖不能使用(threadB未釋放localB鎖),threadA就停在這里等待localB鎖.隨后threadB也執(zhí)行到第二個同步方法,去訪問localA對象鎖的時(shí)候發(fā)現(xiàn)localA還沒有被釋放(threadA未釋放localA鎖),threadB也停在這里等待localA鎖釋放.就這樣兩個線程都沒辦法繼續(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)不會死鎖的時(shí)候應(yīng)該是打印四條log的,這里明顯的出現(xiàn)了死鎖的現(xiàn)象.
死鎖出現(xiàn)的原因
當(dāng)我們了解在什么情況下會產(chǎn)生死鎖,以及什么是死鎖的時(shí)候,我們在寫代碼的時(shí)候應(yīng)該盡量的去避免這個誤區(qū).產(chǎn)生死鎖必須同時(shí)滿足以下四個條件,只要其中任一條件不成立,死鎖就不會發(fā)生.
- 互斥條件:線程要求對所分配的資源進(jìn)行排他性控制,即在一段時(shí)間內(nèi)某 資源僅為一個進(jìn)程所占有.此時(shí)若有其他進(jìn)程請求該資源.則請求進(jìn)程只能等待.
- 不剝奪條件:進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能由獲得該資源的線程自己來釋放(只能是主動釋放).
- 請求和保持條件:線程已經(jīng)保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他線程占有,此時(shí)請求線程被阻塞,但對自己已獲得的資源保持不放.
- 循環(huán)等待條件:存在一種線程資源的循環(huán)等待鏈,鏈中每一個線程已獲得的資源同時(shí)被鏈中下一個線程所請求。
死鎖的解決方法
說實(shí)話避免死鎖還得再自己寫代碼的時(shí)候注意一下.這里引用別人的解決方法,不過我對于這些解決方法不是太懂,講的太含糊沒有具體的實(shí)例.
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
nacos gateway動態(tài)路由實(shí)戰(zhàn)
這篇文章主要介紹了nacos gateway動態(tài)路由實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Mybatis-Plus多表關(guān)聯(lián)查詢的使用案例解析
這篇文章主要介紹了Mybatis-Plus多表關(guān)聯(lián)查詢的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
mybatis-generator生成多次重復(fù)代碼問題以及解決
在使用MySQL數(shù)據(jù)庫時(shí),如果多個數(shù)據(jù)庫中存在相同表名,即使在URL中配置了數(shù)據(jù)庫名,也可能導(dǎo)致數(shù)據(jù)互相影響,解決這一問題的方法是在mapper-generator-config.xml文件中添加catalog屬性,明確指定逆向工程代碼所涉及表的數(shù)據(jù)庫名2024-10-10
Java并發(fā)教程之Callable和Future接口詳解
Java從發(fā)布的第一個版本開始就可以很方便地編寫多線程的應(yīng)用程序,并在設(shè)計(jì)中引入異步處理,這篇文章主要給大家介紹了關(guān)于Java并發(fā)教程之Callable和Future接口的相關(guān)資料,需要的朋友可以參考下2021-07-07
Java?Socket編程從零到實(shí)戰(zhàn)詳解(完整實(shí)戰(zhàn)案例)
這篇文章主要介紹了Java?Socket編程從零到實(shí)戰(zhàn)詳解,本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-04-04
java虛擬機(jī)JVM類加載機(jī)制原理(面試必問)
這篇文章主要介紹了面試當(dāng)中必會問到的java虛擬機(jī)JVM類加載機(jī)制,非常的詳細(xì),有需要的朋友可以借鑒參考下,歡迎多多交流討論2021-08-08
Spring IOC的三種實(shí)現(xiàn)方式詳解
這篇文章主要介紹了Spring IOC的三種實(shí)現(xiàn)方式,在Spring框架中,IOC通過依賴注入來實(shí)現(xiàn),而依賴注入主要有三種實(shí)現(xiàn)方式,構(gòu)造器注入、Setter注入和字段注入,每種方式都有其特點(diǎn)、適用場景和優(yōu)缺點(diǎn),需要的朋友可以參考下2025-02-02

