死鎖是兩個甚至多個線程被永久阻塞時的一種運行局面,這種局面的生成伴隨着至少兩個線程和兩個或者多個資源。在這裏我已寫好一個簡單的程序,它將會引起死鎖方案然後我們就會明白如何分析它。
Java死鎖範例
ThreadDeadlock.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package
com.journaldev.threads; public
class
ThreadDeadlock { public
static
void
main(String[] args) throws
InterruptedException { Object
obj1 = new
Object(); Object
obj2 = new
Object(); Object
obj3 = new
Object(); Thread
t1 = new
Thread( new
SyncThread(obj1, obj2), "t1" ); Thread
t2 = new
Thread( new
SyncThread(obj2, obj3), "t2" ); Thread
t3 = new
Thread( new
SyncThread(obj3, obj1), "t3" ); t1.start(); Thread.sleep( 5000 ); t2.start(); Thread.sleep( 5000 ); t3.start(); } } class
SyncThread implements
Runnable{ private
Object obj1; private
Object obj2; public
SyncThread(Object o1, Object o2){ this .obj1=o1; this .obj2=o2; } @Override public
void
run() { String
name = Thread.currentThread().getName(); System.out.println(name
+ "
acquiring lock on " +obj1); synchronized
(obj1) { System.out.println(name
+ "
acquired lock on " +obj1); work(); System.out.println(name
+ "
acquiring lock on " +obj2); synchronized
(obj2) { System.out.println(name
+ "
acquired lock on " +obj2); work(); } System.out.println(name
+ "
released lock on " +obj2); } System.out.println(name
+ "
released lock on " +obj1); System.out.println(name
+ "
finished execution." ); } private
void
work() { try
{ Thread.sleep( 30000 ); }
catch
(InterruptedException e) { e.printStackTrace(); } } } |
在上面的程序中同步線程正完成Runnable的接口,它工作的是兩個對象,這兩個對象向對方尋求死鎖而且都在使用同步阻塞。
在主函數中,我使用了三個爲同步線程運行的線程,而且在其中每個線程中都有一個可共享的資源。
這些線程以向第一個對象獲取封鎖這種方式運行。但是當它試着像第二個對象獲取封鎖時,它就會進入等待狀態,因爲它已經被另一個線程封鎖住了。這樣,在線程引起死鎖的過程中,就形成了一個依賴於資源的循環。
當我執行上面的程序時,就產生了輸出,但是程序卻因爲死鎖無法停止。
1
2
3
4
5
6
7
8
9
|
t1
acquiring lock on java.lang.Object@6d9dd520 t1
acquired lock on java.lang.Object@6d9dd520 t2
acquiring lock on java.lang.Object@22aed3a5 t2
acquired lock on java.lang.Object@22aed3a5 t3
acquiring lock on java.lang.Object@218c2661 t3
acquired lock on java.lang.Object@218c2661 t1
acquiring lock on java.lang.Object@22aed3a5 t2
acquiring lock on java.lang.Object@218c2661 t3
acquiring lock on java.lang.Object@6d9dd520 |
在此我們可以清楚地在輸出結果中辨認出死鎖局面,但是在我們實際生活所用的應用中,發現死鎖並將它排除是非常難的。
分析死鎖
爲了分析一個死鎖,我們需要關注下應用中的Java線程轉存,在上一節中我已經解釋瞭如何使用VisualVM收集資料或者jstack應用程序產生線程轉存。
以下就是上述程序的線程轉存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
2012-12-27
19:08:34 Full
thread dump Java HotSpot(TM) 64-Bit Server VM (23.5-b02 mixed mode): "Attach
Listener"
daemon prio=5 tid=0x00007fb0a2814000 nid=0x4007 waiting on condition [0x0000000000000000] java.lang.Thread.State:
RUNNABLE "DestroyJavaVM"
prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000] java.lang.Thread.State:
RUNNABLE "t3"
prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000] java.lang.Thread.State:
BLOCKED (on object monitor) at
com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) -
waiting to lock <0x000000013df2f658> (a java.lang. Object ) -
locked <0x000000013df2f678> (a java.lang. Object ) at
java.lang.Thread.run(Thread.java:722) "t2"
prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000] java.lang.Thread.State:
BLOCKED (on object monitor) at
com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) -
waiting to lock <0x000000013df2f678> (a java.lang. Object ) -
locked <0x000000013df2f668> (a java.lang. Object ) at
java.lang.Thread.run(Thread.java:722) "t1"
prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000] java.lang.Thread.State:
BLOCKED (on object monitor) at
com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) -
waiting to lock <0x000000013df2f668> (a java.lang. Object ) -
locked <0x000000013df2f658> (a java.lang. Object ) at
java.lang.Thread.run(Thread.java:722) "Service
Thread"
daemon prio=5 tid=0x00007fb0a1038000 nid=0x5303 runnable [0x0000000000000000] java.lang.Thread.State:
RUNNABLE "C2
CompilerThread1"
daemon prio=5 tid=0x00007fb0a1037000 nid=0x5203 waiting on condition [0x0000000000000000] java.lang.Thread.State:
RUNNABLE "C2
CompilerThread0"
daemon prio=5 tid=0x00007fb0a1016000 nid=0x5103 waiting on condition [0x0000000000000000] java.lang.Thread.State:
RUNNABLE "Signal
Dispatcher"
daemon prio=5 tid=0x00007fb0a4003000 nid=0x5003 runnable [0x0000000000000000] java.lang.Thread.State:
RUNNABLE "Finalizer"
daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object .wait()
[0x000000015d0c0000] java.lang.Thread.State:
WAITING (on object monitor) at
java.lang. Object .wait(Native
Method) -
waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock) at
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135) -
locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock) at
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151) at
java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177) "Reference
Handler"
daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object .wait()
[0x000000015cfbd000] java.lang.Thread.State:
WAITING (on object monitor) at
java.lang. Object .wait(Native
Method) -
waiting on <0x000000013de75320> (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 <0x000000013de75320> (a java.lang.ref.Reference$Lock) "VM
Thread"
prio=5 tid=0x00007fb0a2049800 nid=0x3d03 runnable "GC
task thread#0 (ParallelGC)"
prio=5 tid=0x00007fb0a300d800 nid=0x3503 runnable "GC
task thread#1 (ParallelGC)"
prio=5 tid=0x00007fb0a2001800 nid=0x3603 runnable "GC
task thread#2 (ParallelGC)"
prio=5 tid=0x00007fb0a2003800 nid=0x3703 runnable "GC
task thread#3 (ParallelGC)"
prio=5 tid=0x00007fb0a2004000 nid=0x3803 runnable "GC
task thread#4 (ParallelGC)"
prio=5 tid=0x00007fb0a2005000 nid=0x3903 runnable "GC
task thread#5 (ParallelGC)"
prio=5 tid=0x00007fb0a2005800 nid=0x3a03 runnable "GC
task thread#6 (ParallelGC)"
prio=5 tid=0x00007fb0a2006000 nid=0x3b03 runnable "GC
task thread#7 (ParallelGC)"
prio=5 tid=0x00007fb0a2006800 nid=0x3c03 runnable "VM
Periodic Task Thread"
prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition JNI
global references: 114 Found
one Java-level deadlock: ============================= "t3" : waiting
to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang. Object ), which
is held by "t1" "t1" : waiting
to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang. Object ), which
is held by "t2" "t2" : waiting
to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang. Object ), which
is held by "t3" Java
stack information for the threads listed above: =================================================== "t3" : at
com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) -
waiting to lock <0x000000013df2f658> (a java.lang. Object ) -
locked <0x000000013df2f678> (a java.lang. Object ) at
java.lang.Thread.run(Thread.java:722) "t1" : at
com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) -
waiting to lock <0x000000013df2f668> (a java.lang. Object ) -
locked <0x000000013df2f658> (a java.lang. Object ) at
java.lang.Thread.run(Thread.java:722) "t2" : at
com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) -
waiting to lock <0x000000013df2f678> (a java.lang. Object ) -
locked <0x000000013df2f668> (a java.lang. Object ) at
java.lang.Thread.run(Thread.java:722) Found
1 deadlock. |
這三個線程轉存的輸出清楚地說明了死鎖環境和線程,以及包含死鎖環境的資源。
爲了分析死鎖,我們需要關注死鎖狀態的線程,然後資源再等待去封鎖,每一個資源都有一個獨特的ID,有了這個ID我們就能發現是哪一個進程已經封鎖住對象。舉個例子,線程“t3”正在等待封鎖0x000000013df2f658,但是它已經被線程“t1”封鎖住了。
當我們分析死鎖環境的時候,如果發現線程正在引起死鎖,這是我們就要改變代碼來避免死鎖的產生。
避免死鎖
有很多方針可供我們使用來避免死鎖的局面。
- 避免嵌套封鎖:這是死鎖最主要的原因的,如果你已經有一個資源了就要避免封鎖另一個資源。如果你運行時只有一個對象封鎖,那是幾乎不可能出現一個死鎖局面的。例如,這裏是另一個運行中沒有嵌套封鎖的run()方法,而且程序運行沒有死鎖局面,運行得很成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
void
run() { String
name = Thread.currentThread().getName(); System.out.println(name
+ "
acquiring lock on "
+ obj1); synchronized
(obj1) { System.out.println(name
+ "
acquired lock on "
+ obj1); work(); } System.out.println(name
+ "
released lock on "
+ obj1); System.out.println(name
+ "
acquiring lock on "
+ obj2); synchronized
(obj2) { System.out.println(name
+ "
acquired lock on "
+ obj2); work(); } System.out.println(name
+ "
released lock on "
+ obj2); System.out.println(name
+ "
finished execution." ); } |
- 只對有請求的進行封鎖:你應當只想你要運行的資源獲取封鎖,比如在上述程序中我在封鎖的完全的對象資源。但是如果我們只對它所屬領域中的一個感興趣,那我們應當封鎖住那個特殊的領域而並非完全的對象。
- 避免無限期的等待:如果兩個線程正在等待對象結束,無限期的使用線程加入,如果你的線程必須要等待另一個線程的結束,若是等待進程的結束加入最好準備最長時間。