Java并發基礎實踐--死鎖
本文是
Java并發基礎實踐系列中的一篇,介紹了最簡單的死鎖場景,并使用jstack產生的thread dump來查找死鎖。(2013.12.29最后更新)
1. 死鎖為了能夠維護線程的安全性,Java提供的鎖機制,但不恰當地使用鎖則可能產生死鎖。死鎖是并發編程中一個無法繞開的問題。只要在一個任務中使用了一個以上的鎖,那么就存在死鎖的風險。
死鎖產生的直接原因非常簡單,即兩個線程在相互等待對方所執有的鎖。
2. 鎖順序死鎖在死鎖場景中,最典型的就是鎖順序死鎖,代碼清單1就是一個很常見的示例。
清單1
public class DeadLock {
private Object leftLock = new Object();
private Object rightLock = new Object();
public void leftRight() {
synchronized (leftLock) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (rightLock) {
System.out.println("leftRight");
}
}
}
public void rightLeft() {
synchronized (rightLock) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (leftLock) {
System.out.println("leftRight");
}
}
}
public static void main(String[] args) {
final DeadLock deadLock = new DeadLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
deadLock.leftRight();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
deadLock.rightLeft();
}
});
t1.start();
t2.start();
}
}
3. Thread DumpJDK提供了一組命令行工具,其中就包括jstack。通過jstack可以獲取當前正運行的Java進程的java stack和native stack信息。如果Java進程崩潰了,也可以通過它來獲取core file中的java stack和native stack信息,以方便我們定位問題。
為了能夠使用jstack去輸出目標Java進程的thread dump,首先必須要弄清楚在執行清單1的程序時,該程序的進程號。JDK提供的另一個命令行工具jps可以獲取系統中所有Java進程的相關信息。
在命令行窗口中執行命令
jps,即可以得到清單2所示的結果
清單2
C:\Documents and Settings\Administrator>jps
2848
4552 DeadLock
5256 Jps
其中
4552就是在筆者機器上執行程序DeadLock時所生成Java進程的進程號。
然后再執行命令
jstack 4552,在筆者的機器上就會得到清單3所示的結果
清單3
C:\Documents and Settings\Administrator>jstack 4552
2013-12-29 18:45:41
Full thread dump Java HotSpot(TM) Client VM (23.25-b01 mixed mode, sharing):
"DestroyJavaVM" prio=6 tid=0x00878800 nid=0xd00 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" prio=6 tid=0x02b56c00 nid=0x14ec waiting for monitor entry [0x02fdf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:33)
- waiting to lock <0x22be6598> (a java.lang.Object)
- locked <0x22be65a0> (a java.lang.Object)
at concurrency.deadlock.DeadLock$2.run(DeadLock.java:53)
at java.lang.Thread.run(Thread.java:724)
"Thread-0" prio=6 tid=0x02b55c00 nid=0x354 waiting for monitor entry [0x02f8f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:19)
- waiting to lock <0x22be65a0> (a java.lang.Object)
- locked <0x22be6598> (a java.lang.Object)
at concurrency.deadlock.DeadLock$1.run(DeadLock.java:45)
at java.lang.Thread.run(Thread.java:724)
"Service Thread" daemon prio=6 tid=0x02b34800 nid=0x133c runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread0" daemon prio=10 tid=0x02b13800 nid=0x10fc waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" daemon prio=10 tid=0x02b11c00 nid=0x1424 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" daemon prio=10 tid=0x02b10800 nid=0x1100 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" daemon prio=8 tid=0x02af4c00 nid=0x1238 in Object.wait() [0x02daf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
- locked <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:189)
"Reference Handler" daemon prio=10 tid=0x02af0000 nid=0x12e8 in Object.wait() [0x02d5f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x22b60da0> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:503)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
- locked <0x22b60da0> (a java.lang.ref.Reference$Lock)
"VM Thread" prio=10 tid=0x02aee400 nid=0x129c runnable
"VM Periodic Task Thread" prio=10 tid=0x02b48000 nid=0x89c waiting on condition
JNI global references: 117
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x02af4a3c (object 0x22be6598, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x02af310c (object 0x22be65a0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:33)
- waiting to lock <0x22be6598> (a java.lang.Object)
- locked <0x22be65a0> (a java.lang.Object)
at concurrency.deadlock.DeadLock$2.run(DeadLock.java:53)
at java.lang.Thread.run(Thread.java:724)
"Thread-0":
at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:19)
- waiting to lock <0x22be65a0> (a java.lang.Object)
- locked <0x22be6598> (a java.lang.Object)
at concurrency.deadlock.DeadLock$1.run(DeadLock.java:45)
at java.lang.Thread.run(Thread.java:724)
Found 1 deadlock.
在上述輸出中,我們可以很明確地看到一個死鎖
"Thread-1":
waiting to lock monitor 0x02af4a3c (object 0x22be6598, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x02af310c (object 0x22be65a0, a java.lang.Object),
which is held by "Thread-1"
并且它還標明了程序是在哪個地方時發現了上述死鎖
"Thread-1":
at concurrency.deadlock.DeadLock.rightLeft(DeadLock.java:33)
- waiting to lock <0x22be6598> (a java.lang.Object)
- locked <0x22be65a0> (a java.lang.Object)
at concurrency.deadlock.DeadLock$2.run(DeadLock.java:53)
at java.lang.Thread.run(Thread.java:724)
"Thread-0":
at concurrency.deadlock.DeadLock.leftRight(DeadLock.java:19)
- waiting to lock <0x22be65a0> (a java.lang.Object)
- locked <0x22be6598> (a java.lang.Object)
at concurrency.deadlock.DeadLock$1.run(DeadLock.java:45)
at java.lang.Thread.run(Thread.java:724)
4. 小結死鎖產生的直接原因非常簡單,即兩個線程在相互等待對方所執有的鎖。鎖順序死鎖是其中最經典的場景,此外還有動態的鎖順序死鎖。雖然表現形式有所不同,但本質上都是兩個線程在以不同的順序來獲取相同鎖時,發生了死鎖問題。
使用thread dump可以幫助我們分析死鎖產生的原因。除了直接使用jstack命令來獲取thread dump輸出以外,JDK還提供了jvisualvm工具,它能以可視化的方式展示Java程序的進程號并導出thread dump。