最近遇到了死鎖的問題,所以這里分析并總結(jié)下死鎖,給出一套排查解決方案。
死鎖示例一
清單一
public class SynchronizedDeadLock {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
/**
* ThreadA先獲取lockA,在獲取lockB
*/
private static class ThreadA extends java.lang.Thread {
@Override
public void run() {
// 獲取臨界區(qū)A
synchronized (lockA) {
System.out.println("get lockA success");
// 模擬耗時操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 獲取臨界區(qū)B
synchronized (lockB) {
System.out.println("get lockB success");
}
}
}
}
/**
* ThreadB先獲取lockB,在獲取lockA
*/
private static class ThreadB extends java.lang.Thread {
@Override
public void run() {
// 獲取臨界區(qū)A
synchronized (lockB) {
System.out.println("get lockB success");
// 模擬耗時操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 獲取臨界區(qū)B
synchronized (lockA) {
System.out.println("get lockA success");
}
}
}
}
}
清單一代碼有點長,但是邏輯很簡單,有兩個臨界區(qū)變量lockA,lockB,線程A先獲取到lockA在獲取lockB,線程B則與之相反順序獲取鎖,那么就可能會有以下情況:
線程A獲取到lockA之后發(fā)現(xiàn)lockB已被線程B獲取,那么此時線程A進入blocked狀態(tài)。同理線程B獲取lockA時發(fā)現(xiàn)其被線程A獲取,那么線程B也進入blocked狀態(tài),那么這就是死鎖。
可以總結(jié)下,這種類型的死鎖源于鎖的嵌套,由于線程與線程之間的互相看對方都是亂序執(zhí)行,因此加鎖的順序和釋放順序都是難以保證的,鎖的互相嵌套在多線程下是一個很危險的操作,因此需要額外注意。
可以總結(jié)下,這種類型的死鎖源于鎖的嵌套,由于線程與線程之間的互相看對方都是亂序執(zhí)行,因此加鎖的順序和釋放順序都是難以保證的,鎖的互相嵌套在多線程下是一個很危險的操作,因此需要額外注意。
死鎖示例二
清單二
public class TreeNode {
TreeNode parent = null;
List children = new ArrayList();
public synchronized void addChild(TreeNode child){
if(!this.children.contains(child)) {
this.children.add(child);
child.setParentOnly(this);
}
}
public synchronized void addChildOnly(TreeNode child){
if(!this.children.contains(child)){
this.children.add(child);
}
}
public synchronized void setParent(TreeNode parent){
this.parent = parent;
parent.addChildOnly(this);
}
public synchronized void setParentOnly(TreeNode parent){
this.parent = parent;
}
}
清單2的代碼來自并發(fā)編程網(wǎng)-死鎖,下方代碼可以理解為一個組合模式,那么在多線程的環(huán)境下如果線程1調(diào)用parent.addChild(child)方法的同時有另外一個線程2調(diào)用child.setParent(parent)方法,兩個線程中的parent表示的是同一個對象,child亦然,此時就會發(fā)生死鎖。下面的偽代碼說明了這個過程:
Thread 1: parent.addChild(child); //locks parent
--> child.setParentOnly(parent);
Thread 2: child.setParent(parent); //locks child
--> parent.addChildOnly()
也可以總結(jié)下:這種類型的死鎖本質(zhì)原因也是鎖的嵌套問題,child.setParent(parent)該方法執(zhí)行首先需要獲取到child這個對象鎖,然后其內(nèi)部調(diào)用parent的方法則需要獲取parent的對象鎖,那么就形成了鎖嵌套,因此會出現(xiàn)死鎖。
死鎖示例三
清單三是一種開發(fā)人員經(jīng)常犯的錯誤,一般都是由于某些中斷操作沒有釋放掉鎖,所以也叫(Resource deadlock)比如下方的當i==5直接拋出異常,導致鎖沒有釋放,所以對于資源釋放語句一定要卸載finally中。
public void hello(int i) {
LOCK.lock();
System.out.println(Thread.currentThread().getName() + "--hello:"+i);
// 異常拋出但是沒有釋放掉鎖
if (i == 5) {
throw new IllegalArgumentException("拋出異常,模擬獲取鎖后不釋放");
}
LOCK.unlock();
}
這種死鎖最可怕的地方是難以排查,使用jstack時無法分析出這一類的死鎖,你大概能得到的反饋可能線程仍然處于RUNNABLE,具體排查方法看下方的死鎖排查。
死鎖的排查
jstack or jcmd
jstack與jcmd是JDK自帶的工具包,使用jstack -l pid或者jcmd pid Thread.print可以查看當前應用的進程信息,如果有死鎖也會分析出來。比如清單一中的死鎖會分析出以下結(jié)果:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007fbea28989b8 (object 0x000000076ac710a0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007fbea480a158 (object 0x000000076ac710b0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at cn.mrdear.custom.lock.SynchronizedDeadLock$ThreadB.run(SynchronizedDeadLock.java:72)
- waiting to lock <0x000000076ac710a0> (a java.lang.Object)
- locked <0x000000076ac710b0> (a java.lang.Object)
"Thread-0":
at cn.mrdear.custom.lock.SynchronizedDeadLock$ThreadA.run(SynchronizedDeadLock.java:48)
- waiting to lock <0x000000076ac710b0> (a java.lang.Object)
- locked <0x000000076ac710a0> (a java.lang.Object)
Found 1 deadlock.
在分析中明確指出發(fā)現(xiàn)了死鎖,是由于Thread-1與Thread-0鎖的互斥導致的死鎖。
有時候文件分析不是很容易看,此時可以借助一些工具來分析,比如
http://gceasy.io/,其分析整理后使得結(jié)果更加容易看到。
資源死鎖排查
由于資源沒釋放的死鎖使用jstack等手段難以排查,這種棘手的問題一般要多次dump線程快照,參考kabutz/DeadlockLabJavaOne2012給出的經(jīng)驗主要有以下兩種方式排查:
能夠控制資源死鎖的情況:
- 在死鎖前dump出線程快照
- 在死鎖后再次dump出線程快照
- 兩者比較
已經(jīng)死鎖
- 每隔一段時間dump出線程快照
- 對比找到不會改變的那些線程再排查問題
應用自行檢查
在Java中提供了ThreadMXBean類可以幫助開發(fā)者查找死鎖,該查找效果與jstack一致,對于資源釋放不當死鎖是無法排查的。
使用方法如清單4所示,要注意的是死鎖的排查不是一個很高效的流程,要注意對應用性能的影響。
清單四
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadsIds = threadMXBean.findDeadlockedThreads();
參考
http://ifeve.com/deadlock/
https://github.com/kabutz/DeadlockLabJavaOne2012
- 版權(quán)聲明: 感謝您的閱讀,本文由屈定's Blog版權(quán)所有。如若轉(zhuǎn)載,請注明出處。
- 文章標題: Java--死鎖以及死鎖的排查
- 文章鏈接: https://mrdear.cn/2018/06/16/java/Java_study--deadlock/