本篇結(jié)構(gòu):
- 前言
- 什么是死鎖
- 產(chǎn)生死鎖的必要條件
- 死鎖的代碼示例
- 死鎖排查
- 如何避免死鎖
- 總結(jié)
一、前言
今天被問到什么是死鎖,腦袋一抽,半天沒想起來,想來就是不夠熟悉了,就趁這個點兒記錄一下,也順便了解下分析死鎖的方法,之前這塊的經(jīng)驗的確不算多。
二、什么是死鎖
那什么是死鎖呢?
百度百科的解釋:死鎖是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。
舉個例子:生活中經(jīng)常能看到很窄的巷子只能允許一輛車通過,但是有時就會出現(xiàn)兩輛車迎頭遇上的情況,這時候甲車等乙車讓出路,乙車又等甲車讓出路,甲車乙車互不相讓,造成的結(jié)果就是兩輛車卡在巷子里,都不能動了。
這個例子反映在程序中就是死鎖。
線程一持有鎖A,接著想獲取鎖B,而線程二持有鎖B,接著想獲取鎖A,結(jié)果就是線程一等不到鎖B,線程二等不到鎖A,造成死鎖。
三、產(chǎn)生死鎖的必要條件
- 互斥條件:就是一個資源同時只能有一個進(jìn)程占有,不能有兩個或是兩個以上的占有。
- 不可搶占條件:在一個進(jìn)程所獲取的資源在未使用完畢之前,資源申請者不能強行的從資源占有者手中搶奪資源。
- 占有申請條件:進(jìn)程已經(jīng)占有了一個資源,此時又申請新的資源;但這個新申請的資源已經(jīng)被別的進(jìn)程占有了,該進(jìn)程就會阻塞,在獲取申請的資源之前他還會一直占有已占有的那個資源。
- 循環(huán)等待條件:存在一個循環(huán)等待序列,p1等待p2,p2等待p3,p3等待p1。形成一個進(jìn)程循環(huán)等待。
上述四個條件在死鎖是會同時發(fā)送,也就是只要一個必要條件不通過,則就不會產(chǎn)生死鎖。
四、死鎖的代碼示例
public class TestDeadLock {
private static Lock lockA = new ReentrantLock();
private static Lock lockB = new ReentrantLock();
public static void main(String args[]) {
new Thread(() -> {
try {
lockA.lock();
TimeUnit.SECONDS.sleep(2);
try {
lockB.lock();
} finally {
lockB.unlock();
}
} catch (InterruptedException e) {
} finally {
lockA.unlock();
}
}).start();
new Thread(() -> {
try {
lockB.lock();
TimeUnit.SECONDS.sleep(2);
try {
lockA.lock();
} finally {
lockA.unlock();
}
} catch (InterruptedException e) {
} finally {
lockB.unlock();
}
}).start();
}
}
如上,是一個死鎖的例子,運行代碼會發(fā)現(xiàn)程序被阻塞,如果不進(jìn)行人為干預(yù),程序一直卡住。
相信看過上面代碼都能很快找到問題,一個線程先持有了lockA,然后請求lockB,而另一個線程先持有了lockB,然后請求lockA,結(jié)果就是線程一等不到lockB(已經(jīng)被線程二持有),線程二等不到lockA(已經(jīng)被線程一持有),相互等待造成死鎖。
五、死鎖排查
這是我們主動創(chuàng)造的死鎖,很容易定位,但現(xiàn)實代碼中往往是沒這么直觀的,該怎么分析呢?
可以利用jdk的bin目錄下的jvisualvm.exe工具,雙擊運行可以看到如下的圖形界面:

左邊是JVM進(jìn)程列表,找到對應(yīng)的進(jìn)程雙擊查看,切換到線程欄目,可以看到右側(cè)的信息,也特意截了個圖:

可以看到正上方有檢測到死鎖的紅字提示,接著看線程的狀況,也可以發(fā)現(xiàn)是駐留的狀態(tài)。
已經(jīng)知道出現(xiàn)了死鎖,就要確定哪里出現(xiàn)了死鎖,可以點擊右上方的線程Dump按鈕,dump出可以找到一些關(guān)鍵信息:

大致是說:線程1在等待線程0持有的資源,線程0在等待線程1持有的資源。
接著看可以找到哪里出現(xiàn)死鎖:

說36行和20行出現(xiàn)了死鎖。
六、如何避免死鎖
日常開發(fā)應(yīng)該盡量避免死鎖的產(chǎn)生。
- 盡量不要寫順序不一致加鎖的代碼
- 如果必須要寫順序不一致的加鎖的代碼,可以采用trylock 方法,避免程序一直等待
- 預(yù)防死鎖可以采用finddebug插件去掃描代碼發(fā)現(xiàn)死鎖
如果出現(xiàn)了死鎖,大約只能停止程序,代價非常大,所以要盡量避免死鎖。
七、總結(jié)
上面大約都是紙上談兵,各位做個參考即可,能有點幫助是最好了。