Java多线程经典问题

一道经典的多线程协同问题:三个线程依次打印A,B,C

ThreadPrinter.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
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
package pw.gouzai;

/**
* 有点意思的题目,刺激了。也加强了自身的思维逻辑
* 当线程执行obj.wait()方法,虚拟机就会把它放入obj的等待队列,直到持有执行wait的方法obj对象执行了notify()/notifyAll()方法,此线程才会从线程等待中唤醒(未必是执行,还得继续抢执行权)
*/
public class ThreadPrinter implements Runnable {

private String name;
private Object prev;
private Object self;

public ThreadPrinter(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}

/**
* 运行情况分析:
*
* 第一种情况:abcabcabc
*
* 线程a,b,c在运行之后都会休眠10毫秒保证线程按照顺序执行,是想先让线程a申请c锁,然后释放a锁的时候线程b再启动申请a锁,再释放b锁的时候让线程c启动申请a锁。这样子就会成为环环相扣。下一个人的锁是在上一个人手上,只能是由上一个人唤醒。
* 第一次打印abc的时候:
* synchronized
* 线程a
* synchronized(c)//获取c锁
* synchronized(a)//获取自身锁
* a.notify()//释放自身锁,让线程b去申请a锁然后执行(线程b已经启动,正在等待锁)
* c.wait()//使线程c释放锁,使其暂停在此(线程c还没启动)
* 线程b
* synchronized(a)//获取a锁
* synchronized(b)//获取自身锁
* b.notify()//释放自身锁,让线程c申请b锁然后执行(线程c已经启动,正在等待锁)
* a.wait()//使线程a释放锁,使其暂停在此
* 线程c
* synchronized(b)//获取b锁
* synchronized(c)//获取自身锁
* c.notify()//释放自身锁,线程a正在等候此锁
* b.wait()//使线程b释放锁,使其暂停在此
*
*
* 第二次打印abc的时候:
* synchronized
* 线程a
* synchronized(c)//获取c锁
* synchronized(a)//获取自身锁
* a.notify()//释放自身锁,线程b正在等候此锁,线程b申请锁
* c.wait()//使线程c释放锁,让其暂停在此
* 线程b
* synchronized(a)//获取a锁
* synchronized(b)//获取自身锁
* b.notify()//释放自身锁,线程c正在等候此锁,线程c申请锁
* a.wait()//使线程a释放锁,让其暂停在此
* 线程c
* synchronized(b)//获取b锁
* synchronized(c)//获取自身锁
* c.notify()//释放自身锁,线程a正在等候此锁,线程a申请锁
* b.wait()//使线程b释放锁,让其等候在此
*
* 最后一次打印abc的时候:
* synchronized
* 线程a
* synchronized(c)//获取c锁
* synchronized(a)//获取自身锁
* a.notify()//释放自身锁,线程b正在等候此锁,线程b申请锁
* c.wait()//使线程c释放锁,让其暂停在此
* 线程b
* synchronized(a)//获取a锁
* synchronized(b)//获取自身锁
* b.notify()//释放自身锁,线程c正在等候此锁,线程c申请锁
* a.wait()//使线程a释放锁,让其暂停在此
* 线程c
* synchronized(b)//获取b锁
* synchronized(c)//获取自身锁
* c.notify()//释放自身锁,线程a正在等候此锁,线程a申请锁
* b.wait()//使线程b释放锁,让其等候在此
*
*此时已经执行三次,线程c是最后一次执行的。使线程b休眠,线程b使线程a休眠。线程c最后一次操作中唤醒了线程a。最后的结果是线程a运行完毕。在执行while(count>0)的时候,count=0.使得线程a无法进行循环,所以无法唤醒正在等待a锁的线程b跟等待b锁的线程c。就会造成死锁。
*
* 第二种情况:acbacbacb
* 那就是线程a在释放自身锁之前,线程b在等候a锁,那么线程b就不能运行,线程c就会申请b锁,再接着就是线程b申请a锁,那么就会出现a
*
* 分析过程如上。
*/

@Override
public void run() {
int count = 3;//打印3次(次数小容易分析)
while(count>0) {
synchronized(prev){
synchronized (self){
System.err.println(name);
count--;
self.notify();//唤醒去竞争锁(不会释放,只会通知),退出synchronized代码块后,当前线程才会释放锁
}
try {
prev.wait();//让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Object c = new Object();

ThreadPrinter tpa = new ThreadPrinter("A",c,a);
ThreadPrinter tpb = new ThreadPrinter("B",a , b);
ThreadPrinter tpc = new ThreadPrinter("C", b, c);

new Thread(tpa).start();
Thread.sleep(100);
new Thread(tpb).start();
Thread.sleep(100);
new Thread(tpc).start();
Thread.sleep(10);
}
}

1
2
3
4
5
6
7
8
9
A
B
C
A
B
C
A
B
C

死锁证明以及解决demo,仅供参考
ThreadPrinter.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
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
181
182
183
package pw.gouzai;

/**
* 有点意思的题目,刺激了。也加强了自身的思维逻辑
* 当线程执行obj.wait()方法,虚拟机就会把它放入obj的等待队列,直到持有执行wait的方法obj对象执行了notify()/notifyAll()方法,此线程才会从线程等待中唤醒(未必是执行,还得继续抢执行权)
*/
public class ThreadPrinter implements Runnable {

private String name;
private Object prev;
private Object self;

private Object a;
private Object b;
private Object c;

public ThreadPrinter(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}

public Object getA() {
return a;
}

public void setA(Object a) {
this.a = a;
}

public Object getB() {
return b;
}

public void setB(Object b) {
this.b = b;
}

public Object getC() {
return c;
}

public void setC(Object c) {
this.c = c;
}

/**
* 运行情况分析:
*
* 第一种情况:abcabcabc
*
* 线程a,b,c在运行之后都会休眠10毫秒保证线程按照顺序执行,是想先让线程a申请c锁,然后释放a锁的时候线程b再启动申请a锁,再释放b锁的时候让线程c启动申请a锁。这样子就会成为环环相扣。下一个人的锁是在上一个人手上,只能是由上一个人唤醒。
* 第一次打印abc的时候:
* synchronized
* 线程a
* synchronized(c)//获取c锁
* synchronized(a)//获取自身锁
* a.notify()//释放自身锁,让线程b去申请a锁然后执行(线程b已经启动,正在等待锁)
* c.wait()//使线程c释放锁,使其暂停在此(线程c还没启动)
* 线程b
* synchronized(a)//获取a锁
* synchronized(b)//获取自身锁
* b.notify()//释放自身锁,让线程c申请b锁然后执行(线程c已经启动,正在等待锁)
* a.wait()//使线程a释放锁,使其暂停在此
* 线程c
* synchronized(b)//获取b锁
* synchronized(c)//获取自身锁
* c.notify()//释放自身锁,线程a正在等候此锁
* b.wait()//使线程b释放锁,使其暂停在此
*
*
* 第二次打印abc的时候:
* synchronized
* 线程a
* synchronized(c)//获取c锁
* synchronized(a)//获取自身锁
* a.notify()//释放自身锁,线程b正在等候此锁,线程b申请锁
* c.wait()//使线程c释放锁,让其暂停在此
* 线程b
* synchronized(a)//获取a锁
* synchronized(b)//获取自身锁
* b.notify()//释放自身锁,线程c正在等候此锁,线程c申请锁
* a.wait()//使线程a释放锁,让其暂停在此
* 线程c
* synchronized(b)//获取b锁
* synchronized(c)//获取自身锁
* c.notify()//释放自身锁,线程a正在等候此锁,线程a申请锁
* b.wait()//使线程b释放锁,让其等候在此
*
* 最后一次打印abc的时候:
* synchronized
* 线程a
* synchronized(c)//获取c锁
* synchronized(a)//获取自身锁
* a.notify()//释放自身锁,线程b正在等候此锁,线程b申请锁
* c.wait()//使线程c释放锁,让其暂停在此
* 线程b
* synchronized(a)//获取a锁
* synchronized(b)//获取自身锁
* b.notify()//释放自身锁,线程c正在等候此锁,线程c申请锁
* a.wait()//使线程a释放锁,让其暂停在此
* 线程c
* synchronized(b)//获取b锁
* synchronized(c)//获取自身锁
* c.notify()//释放自身锁,线程a正在等候此锁,线程a申请锁
* b.wait()//使线程b释放锁,让其等候在此
*
*此时已经执行三次,线程c是最后一次执行的。使线程b休眠,线程b使线程a休眠。线程c最后一次操作中唤醒了线程a。最后的结果是线程a运行完毕。在执行while(count>0)的时候,count=0.使得线程a无法进行循环,所以无法唤醒正在等待a锁的线程b跟等待b锁的线程c。就会造成死锁。
*
* 第二种情况:acbacbacb
* 那就是线程a在释放自身锁之前,线程b在等候a锁,那么线程b就不能运行,线程c就会申请b锁,再接着就是线程b申请a锁,那么就会出现a
*
* 分析过程如上。
*/

@Override
public void run() {
int count = 3;//打印3次(次数小容易分析)
while(count>0) {
synchronized(prev){
synchronized (self){
System.err.println(name);
count--;
self.notify();//唤醒去竞争锁(不会释放,只会通知),退出synchronized代码块后,当前线程才会释放锁
}
try {
prev.wait();//让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/**
* 因为线程b,线程c都是阻塞在wait中了,线程a被线程c最后唤醒了,无奈的是coun=0了,无法继续循环了。那么只能执行完毕的是线程a
* 线程a执行完毕了,就得让线程b唤醒完毕,其次就是让线程c唤醒
*/

System.err.println(count);
System.err.println("A".equals(name));
System.err.println("B".equals(name));
synchronized (a){
if("A".equals(name) && count ==0){
a.notify();
}
}
synchronized (b){
if("B".equals(name) && count ==0){
b.notify();
}
}
}

public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Object c = new Object();

ThreadPrinter tpa = new ThreadPrinter("A",c,a);
ThreadPrinter tpb = new ThreadPrinter("B",a , b);
ThreadPrinter tpc = new ThreadPrinter("C", b, c);

tpa.setA(a);
tpa.setB(b);
tpa.setC(c);

tpb.setA(a);
tpb.setB(b);
tpb.setC(c);


tpc.setA(a);
tpc.setB(b);
tpc.setC(c);

new Thread(tpa).start();
Thread.sleep(100);
new Thread(tpb).start();
Thread.sleep(100);
new Thread(tpc).start();
Thread.sleep(10);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
A
B
C
A
B
C
A
B
C
0
true
false
0
false
true
0
false
false

https://chairmanmao.iteye.com/blog/2021081
https://blog.csdn.net/zyplus/article/details/6672775

谢谢,爱你么么哒