盒子
盒子
文章目录
  1. 1. 死锁
    1. 1.1 死锁的必要条件
    2. 1.2 死锁的产生方式
      1. 1.2.1 锁顺序死锁
      2. 1.2.2 动态的锁顺序死锁
      3. 1.2.3 在协作对象之间发生死锁
      4. 1.2.4 在协作对象之间发生死锁
    3. 1.3 死锁的避免与诊断
      1. 1.3.1定义锁的顺序
      2. 1.3.2 开放调用
      3. 1.3.3 使用支持定时的锁
      4. 1.3.4 通过线程转储来分析死锁
  2. 2. 饥饿
  3. 3. 糟糕的响应性
  4. 4. 活锁

避免活跃性风险

1. 死锁

1.1 死锁的必要条件

互斥、 等待、 不可剥夺、 循环等待

1.2 死锁的产生方式

1.2.1 锁顺序死锁

线程A先获取了锁a,尝试获取锁b, 此时,线程B获取了锁b,尝试获取锁a, 产生死锁。

1.2.2 动态的锁顺序死锁

线程A: transferMoney(myAccount, yourAccount, 1);
线程B: transferMoney(yourAccount, myAccount, 1);

由于动态传参的顺序问题,导致死锁,以下代码为死锁案例:

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
public class Temp {

public static void main(String[] args) throws InterruptedException {

// 开设100个账户每个账户100元
Account[] accounts = new Account[100];
for (int i = 0; i < accounts.length; i++) {
accounts[i] = new Temp().new Account();
}
Random random = new Random();

for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 100000; j++) { // 模拟10万次转转账
int fromIndex = random.nextInt(2);
int toIndex = random.nextInt(2);
System.out.println(fromIndex + " " + toIndex);
transferMoney(accounts[fromIndex], accounts[toIndex], 1);
}
}).start();
}
}

public static void transferMoney(Account fromAccount, Account toAccount, int amount) {
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.money < amount) {
throw new IllegalArgumentException("账户余额不足");
}
fromAccount.money -= amount;
toAccount.money += amount;
}
}
}

class Account {
long money = 100000000;
}
}

1.2.3 在协作对象之间发生死锁

类A同步方法中,存在调用类B中同步方法,类B中同步方法,存在调用类A中同步方法

1
2
3
4
5
6
7
8
9
10
11
public class Dispatcher {

public synchronized void getImage (Taxi taxi) {
System.out.println("获取车位置图像");
taxi.getLocation();
}

public synchronized void notifyAvailable(Taxi taxi) {
System.out.println("该车辆已到达");
}
}
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 Taxi {

private Dispatcher dispatcher;

public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}

public synchronized void getLocation() {
System.out.println("获取位置");
}

public synchronized void setLocation() {
System.out.println("设置位置");
if (true) {
dispatcher.notifyAvailable(this);
}
}

public static void main(String[] args) {

Dispatcher dispatcher = new Dispatcher();
Taxi taxi = new Taxi(dispatcher);

Thread threadOne = new Thread(() -> {
taxi.setLocation();
});

Thread threadTwo = new Thread(() -> {
dispatcher.getImage(taxi);
});

threadOne.start();
threadTwo.start();
}
}

注意:

JAVA实例方法同步是同步在该方法的对象实例上,如果每个线程运行的是不同对象实例的同步方法,则没有同步效果

1.2.4 在协作对象之间发生死锁

多个线程相互等待锁而不释放自己持有的锁时会发生死锁;当它们在相同的资源集合上等待上也会发生死锁。

如一个任务需要连接两个数据库,线程A持有数据库D1,需要获取数据库D2,而线程B持有数据库D2,需要获取数据库D1,线程AB因为资源相互等待,产生死锁。

1.3 死锁的避免与诊断

1.3.1定义锁的顺序

针对1.2.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
public class Temp {
private static final Object object = new Object();
......

public static void transferMoney(Account fromAccount, Account toAccount, int amount) {
int fromHash = System.identityHashCode(fromAccount);
int toHash = System.identityHashCode(toAccount);

if (fromHash < toHash) {
synchronized (fromAccount) {
synchronized (toAccount) {
......
}
}
} else if (fromHash > toHash) {
synchronized (toAccount) {
synchronized (fromAccount) {
......
}
}
} else {
synchronized (object) {
synchronized (fromAccount) {
synchronized (toAccount) {
......
}
}
}
}
}
}

1.3.2 开放调用

如果调用某个方法时不需要持有锁,呢么这种调用被称为开放调用。

如在1.2.3协作对象之间发生的死锁,可通过开放调用来解决,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Dispatcher {

public void getImage (Taxi taxi) {
synchronized (this) {
System.out.println("获取车位置图像");
}
taxi.getLocation();
}

public synchronized void notifyAvailable(Taxi taxi) {
System.out.println("该车辆已到达");
}
}
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
public class Taxi {

private Dispatcher dispatcher;

public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}

public synchronized void getLocation() {
System.out.println("获取位置");
}

public void setLocation() {
synchronized (this) {
System.out.println("设置位置");
}
if (true) {
dispatcher.notifyAvailable(this);
}
}

public static void main(String[] args) {

Dispatcher dispatcher = new Dispatcher();
Taxi taxi = new Taxi(dispatcher);

Thread threadOne = new Thread(() -> {
taxi.setLocation();
});

Thread threadTwo = new Thread(() -> {
dispatcher.getImage(taxi);
});

threadOne.start();
threadTwo.start();
}
}

1.3.3 使用支持定时的锁

针对1.2.2动态的锁顺序发生的死锁,我们除了可以定义锁的顺序,还可以使用定时的锁,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Temp {
public static boolean transferMoney(Account fromAccount, Account toAccount, int amount) {
Lock lock = new ReentrantLock();
while (true) {
if (lock.tryLock()) {
try {
if (fromAccount.money < amount) {
throw new IllegalArgumentException("账户余额不足");
}
fromAccount.money -= amount;
toAccount.money += amount;
return true;
} finally {
lock.unlock();
}
}
}
}
}

1.3.4 通过线程转储来分析死锁

UNIX平台按下Ctrl-\键 ,Window下按Ctrl-Break

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
2019-03-17 12:42:45
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"DestroyJavaVM" #32 prio=5 os_prio=0 tid=0x000000001f99f000 nid=0x650 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Thread-16" #28 prio=5 os_prio=0 tid=0x000000001f98f800 nid=0x38e8 waiting for monitor entry [0x000000002135e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:38)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
- locked <0x000000076fe09118> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-13" #25 prio=5 os_prio=0 tid=0x000000001f98c800 nid=0x1584 waiting for monitor entry [0x000000002105e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:38)
- waiting to lock <0x000000076fe09118> (a Temp$Account)
- locked <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-11" #23 prio=5 os_prio=0 tid=0x000000001f990800 nid=0x3254 waiting for monitor entry [0x0000000020e5f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:37)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-10" #22 prio=5 os_prio=0 tid=0x000000001f98b000 nid=0x36e0 waiting for monitor entry [0x0000000020d5e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:37)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-5" #17 prio=5 os_prio=0 tid=0x000000001f982800 nid=0x3b94 waiting for monitor entry [0x000000002075f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:37)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-3" #15 prio=5 os_prio=0 tid=0x000000001f981000 nid=0x1928 waiting for monitor entry [0x000000002055f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:37)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

"Thread-2" #14 prio=5 os_prio=0 tid=0x000000001f97c000 nid=0x15f0 waiting for monitor entry [0x000000002045f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at Temp.transferMoney(Temp.java:37)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

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

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

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

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

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

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001eb89800 nid=0x28ec runnable [0x000000001f33e000]
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 <0x000000076fe30d38> (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 <0x000000076fe30d38> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001eaf8800 nid=0xc8c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001eb53000 nid=0x395c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001eae1800 nid=0x3888 in Object.wait() [0x000000001efbf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076fe24628> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000076fe24628> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

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

"VM Thread" os_prio=2 tid=0x000000001cbd9800 nid=0x1aa4 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000003058800 nid=0x3354 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000305a000 nid=0x2a50 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000305c000 nid=0x3690 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000305e800 nid=0x1e48 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000003060800 nid=0xd38 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000003062000 nid=0x1ff8 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000003065000 nid=0x2ef0 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000003066000 nid=0x1e1c runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001eca5000 nid=0x38e0 waiting on condition

JNI global references: 217


Found one Java-level deadlock:
=============================
"Thread-16":
waiting to lock monitor 0x000000001cbe35b8 (object 0x000000076fe344e0, a Temp$Account),
which is held by "Thread-13"
"Thread-13":
waiting to lock monitor 0x000000001cbe0bc8 (object 0x000000076fe09118, a Temp$Account),
which is held by "Thread-16"

Java stack information for the threads listed above:
===================================================
"Thread-16":
at Temp.transferMoney(Temp.java:38)
- waiting to lock <0x000000076fe344e0> (a Temp$Account)
- locked <0x000000076fe09118> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-13":
at Temp.transferMoney(Temp.java:38)
- waiting to lock <0x000000076fe09118> (a Temp$Account)
- locked <0x000000076fe344e0> (a Temp$Account)
at Temp.lambda$main$0(Temp.java:30)
at Temp$$Lambda$1/1831932724.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

Heap
PSYoungGen total 76288K, used 6337K [0x000000076b380000, 0x0000000770880000, 0x00000007c0000000)
eden space 65536K, 7% used [0x000000076b380000,0x000000076b85e7b0,0x000000076f380000)
from space 10752K, 12% used [0x000000076fe00000,0x000000076ff52040,0x0000000770880000)
to space 10752K, 0% used [0x000000076f380000,0x000000076f380000,0x000000076fe00000)
ParOldGen total 175104K, used 24K [0x00000006c1a00000, 0x00000006cc500000, 0x000000076b380000)
object space 175104K, 0% used [0x00000006c1a00000,0x00000006c1a06000,0x00000006cc500000)
Metaspace used 4940K, capacity 5052K, committed 5248K, reserved 1056768K
class space used 550K, capacity 594K, committed 640K, reserved 1048576K

2. 饥饿

当线程无法访问它所需要的资源并且不能继续执行时,就发生了“饥饿”。引发饥饿的最常见资源就是CPU时钟周期

如果JAVA应用对线程的优先级使用不当或者采用了无法结束的数据结构(无限循环或者无限制的等待),呢么也可能发生饥饿

3. 糟糕的响应性

4. 活锁

活锁尽管不会阻塞线程,但也不能继续执行,因为线程将重复执行相同的操作,并且总会失败。

活锁通常发生在事务消息的应用中,如失败,回滚,将它重新放入队列执行。

支持一下
扫一扫,支持沈健
  • 微信扫一扫
  • 支付宝扫一扫