认识线程死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

20210827160649

下面通过两个例子来说明线程死锁

死锁代码一

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
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2

public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();

new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}

output:

1
2
3
4
Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态指的是系统能够按照某种进行推进顺序(P1、P2、P3…..Pn)来为每个进程分配所需资源,直到满足每个进程对资源的最大需求,使每个进程都可顺利完成。称<P1、P2、P3.....Pn>序列为安全序列。

我们对线程 2 的代码修改成下面这样就不会产生死锁了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 2").start();

Output:

1
2
3
4
5
6
7
8
Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2
Thread[线程 1,5,main]get resource2
Thread[线程 2,5,main]get resource1
Thread[线程 2,5,main]waiting get resource2
Thread[线程 2,5,main]get resource2

Process finished with exit code 0

我们分析一下上面的代码为什么避免了死锁的发生?

线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。

死锁代码二

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
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new HoldLockThread(lockA, lockB), "ThreadAAA").start();
new Thread(new HoldLockThread(lockB, lockA), "ThreadBBB").start();
}
}

// 资源类
class HoldLockThread implements Runnable {

private String lockA;
private String lockB;

public HoldLockThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}

@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockA + "\t 尝试获得:" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockB + "\t 尝试获得:" + lockA);
}
}
}
}

死锁产生的原因

  1. 系统资源不足。
  2. 进程运行推进的顺序不合适。
  3. 资源分配不当。

死锁产生的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

如何避免死锁?

1. 避免嵌套锁
如果您已经持有一个资源,请避免锁定另一个资源。如果只使用一个对象锁,则几乎不可能出现死锁情况。

2. 只锁需要的部分
只获对需要的资源加锁,例如在程序中,我们锁定了完整的对象资源,但是如果我们只需要其中一个字段,那么我们应该只锁定那个特定的字段而不是完整的对象。

3. 避免无限期等待
如果两个线程使用 thread join 无限期互相等待也会造成死锁,我们可以设定等待的最大时间来避免这种情况。

如何排查死锁?(重要)

1. 使用jps命令定位进程号

jps -l

1
2
3
4
5
6
D:\IDE_Project\JavaLearning\JavaThreadDemo>jps -l
21840 org.jetbrains.jps.cmdline.Launcher
23780 org.jetbrains.jps.cmdline.Launcher
16216 sun.tools.jps.Jps
18456 com.marlowe.demos.DeadLockDemo
17404

2. jstack找到死锁查看

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
113
114
115
116
117
118
119
120
121
122
123
124
D:\IDE_Project\JavaLearning\JavaThreadDemo>jstack 18456
2021-08-28 09:41:39
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000003763800 nid=0x5180 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"ThreadBBB" #13 prio=5 os_prio=0 tid=0x000000001f24e800 nid=0x4f08 waiting for monitor entry [0x00000000201bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.marlowe.demos.HoldLockThread.run(DeadLockDemo.java:47)
- waiting to lock <0x000000076b61d588> (a java.lang.String)
- locked <0x000000076b61d5c0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

"ThreadAAA" #12 prio=5 os_prio=0 tid=0x000000001f24d800 nid=0x4c68 waiting for monitor entry [0x00000000200be000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.marlowe.demos.HoldLockThread.run(DeadLockDemo.java:47)
- waiting to lock <0x000000076b61d5c0> (a java.lang.String)
- locked <0x000000076b61d588> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001f1e6800 nid=0x624c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001f152000 nid=0x5c04 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001f149000 nid=0x5784 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001f142000 nid=0x488c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001f133800 nid=0x4e54 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001f131800 nid=0x40a4 runnable [0x000000001f9be000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076b507680> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076b507680> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:48)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001f0f0000 nid=0x2dc0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001f0ef000 nid=0x5ff8 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001f081000 nid=0x5c1c in Object.wait() [0x000000001f65e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001f080800 nid=0x5f5c in Object.wait() [0x000000001f55e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b386c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076b386c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x000000001d289800 nid=0x2b1c runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000003778800 nid=0x59b4 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000377a800 nid=0xcc8 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000377c000 nid=0x4824 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000377e800 nid=0x470c runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000003780800 nid=0x584c runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000003782000 nid=0x183c runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000003785000 nid=0x1680 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000003786000 nid=0x7e4 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001f1ea800 nid=0x28ec waiting on condition

JNI global references: 12


Found one Java-level deadlock:
=============================
"ThreadBBB":
waiting to lock monitor 0x000000001d290c28 (object 0x000000076b61d588, a java.lang.String),
which is held by "ThreadAAA"
"ThreadAAA":
waiting to lock monitor 0x000000001f21fd88 (object 0x000000076b61d5c0, a java.lang.String),
which is held by "ThreadBBB"

Java stack information for the threads listed above:
===================================================
"ThreadBBB":
at com.marlowe.demos.HoldLockThread.run(DeadLockDemo.java:47)
- waiting to lock <0x000000076b61d588> (a java.lang.String)
- locked <0x000000076b61d5c0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"ThreadAAA":
at com.marlowe.demos.HoldLockThread.run(DeadLockDemo.java:47)
- waiting to lock <0x000000076b61d5c0> (a java.lang.String)
- locked <0x000000076b61d588> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

评论