Deadlock — это программная ситуация, когда два или более потоков заблокированы навсегда, такая ситуация возникает как минимум с двумя потоками и двумя или более ресурсами. Здесь я написал простую программу, которая вызовет сценарий тупиковой ситуации, а затем мы увидим, как его анализировать.
Пример тупика Java
|
01
02
03
04
05
06
07
08
09
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
|
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(); } }} |
В вышеупомянутой программе SyncThread реализует интерфейс Runnable и работает с двумя объектами, получая блокировку каждого из них один за другим, используя синхронизированный блок.
В основном методе у меня есть три потока, работающих для SyncThread, и между всеми потоками есть общий ресурс. Потоки запускаются таким образом, что он сможет получить блокировку для первого объекта, но когда он пытается получить блокировку для второго объекта, он переходит в состояние ожидания, поскольку он уже заблокирован другим потоком. Это формирует циклическую зависимость для ресурса между потоками, вызывая взаимоблокировку.
Когда я выполняю вышеупомянутую программу, здесь генерируется вывод, но программа никогда не завершается из-за тупика.
|
1
2
3
4
5
6
7
8
9
|
t1 acquiring lock on java.lang.Object@6d9dd520t1 acquired lock on java.lang.Object@6d9dd520t2 acquiring lock on java.lang.Object@22aed3a5t2 acquired lock on java.lang.Object@22aed3a5t3 acquiring lock on java.lang.Object@218c2661t3 acquired lock on java.lang.Object@218c2661t1 acquiring lock on java.lang.Object@22aed3a5t2 acquiring lock on java.lang.Object@218c2661t3 acquiring lock on java.lang.Object@6d9dd520 |
Здесь мы можем четко определить ситуацию взаимоблокировки по выходным данным, но в реальных приложениях очень сложно найти ситуацию взаимоблокировки и отладить ее.
Анализ тупика
Чтобы проанализировать взаимоблокировку, нам нужно взглянуть на дамп java-потока приложения, в прошлом посте я объяснил, как мы можем сгенерировать дамп потока с помощью профилировщика VisualVM или утилиты jstack.
Вот дамп потока вышеуказанной программы.
|
01
02
03
04
05
06
07
08
09
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
|
2012-12-27 19:08:34Full 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 conditionJNI global references: 114Found 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. |
Вывод дампа потока четко показывает ситуацию взаимоблокировки и задействованные потоки и ресурсы, вызывающие ситуацию взаимоблокировки.
Для анализа взаимоблокировки нам нужно посмотреть на потоки с состоянием как BLOCKED, а затем ресурсы, которые он ожидает блокировки , каждый ресурс имеет уникальный идентификатор, с помощью которого мы можем найти, какой поток уже удерживает блокировку объекта. Например, поток «t3» ожидает блокировки 0x000000013df2f658, но он уже заблокирован потоком «t1».
Как только мы проанализируем ситуацию взаимоблокировки и выясним, какие потоки вызывают взаимоблокировку, нам нужно внести изменения в код, чтобы избежать ситуации взаимоблокировки.
Избегайте тупиковой ситуации
Вот некоторые из руководств, с помощью которых мы можем избежать большинства тупиковых ситуаций.
- Избегайте вложенных блокировок: это наиболее распространенная причина тупиковых ситуаций, избегайте блокирования другого ресурса, если он у вас уже есть. Почти невозможно получить тупиковую ситуацию, если вы работаете только с одной блокировкой объекта. Например, вот еще одна реализация метода run () без вложенной блокировки, и программа успешно работает без ситуации взаимоблокировки.
01020304050607080910111213141516
publicvoidrun() {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.');} - Блокировка только того, что требуется : вы должны получить блокировку только для ресурсов, с которыми вам нужно работать, например, в приведенной выше программе я блокирую весь ресурс объекта, но если нас интересует только одно из его полей, то мы должны заблокировать только это конкретное поле не полный объект.
- Избегайте бесконечного ожидания : вы можете получить взаимоблокировку, если два потока ждут завершения друг друга бесконечно, используя соединение потоков . Если вашему потоку приходится ждать завершения другого потока, всегда лучше использовать соединение с максимальным временем ожидания завершения потока.
Ссылка: пример Java Deadlock — Как проанализировать ситуацию тупика от нашего партнера по JCG Панкаджа Кумара в блоге Developer Recipes .