Статьи

Пример тупика Java — Как проанализировать ситуацию тупика

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@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.

Вот дамп потока вышеуказанной программы.

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: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.

Вывод дампа потока четко показывает ситуацию взаимоблокировки и задействованные потоки и ресурсы, вызывающие ситуацию взаимоблокировки.

Для анализа взаимоблокировки нам нужно посмотреть на потоки с состоянием как BLOCKED, а затем ресурсы, которые он ожидает блокировки , каждый ресурс имеет уникальный идентификатор, с помощью которого мы можем найти, какой поток уже удерживает блокировку объекта. Например, поток «t3» ожидает блокировки 0x000000013df2f658, но он уже заблокирован потоком «t1».

Как только мы проанализируем ситуацию взаимоблокировки и выясним, какие потоки вызывают взаимоблокировку, нам нужно внести изменения в код, чтобы избежать ситуации взаимоблокировки.

Избегайте тупиковой ситуации

Вот некоторые из руководств, с помощью которых мы можем избежать большинства тупиковых ситуаций.

  • Избегайте вложенных блокировок: это наиболее распространенная причина тупиковых ситуаций, избегайте блокирования другого ресурса, если он у вас уже есть. Почти невозможно получить тупиковую ситуацию, если вы работаете только с одной блокировкой объекта. Например, вот еще одна реализация метода run () без вложенной блокировки, и программа успешно работает без ситуации взаимоблокировки.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    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.');
    }
  • Блокировка только того, что требуется : вы должны получить блокировку только для ресурсов, с которыми вам нужно работать, например, в приведенной выше программе я блокирую весь ресурс объекта, но если нас интересует только одно из его полей, то мы должны заблокировать только это конкретное поле не полный объект.
  • Избегайте бесконечного ожидания : вы можете получить взаимоблокировку, если два потока ждут завершения друг друга бесконечно, используя соединение потоков . Если вашему потоку приходится ждать завершения другого потока, всегда лучше использовать соединение с максимальным временем ожидания завершения потока.

Ссылка: пример Java Deadlock — Как проанализировать ситуацию тупика от нашего партнера по JCG Панкаджа Кумара в блоге Developer Recipes .