一道有意思的多线程编程题

题目描述

实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程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
36
37
38
39
public class MyContainer2 {

//添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
MyContainer2 c = new MyContainer2();

new Thread(() -> {
for(int i=0; i<10; i++) {
c.add(new Object());
System.out.println("add " + i);

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();

new Thread(() -> {
while(true) {
if(c.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}

这样给 lists 添加 volatile 之后,t2能够接到通知,但是t2线程的死循环很浪费cpu,再看看下面这个方法。

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

//添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
MyContainer4 c = new MyContainer4();

final Object lock = new Object();

new Thread(() -> {
synchronized(lock) {
System.out.println("t2启动");
if(c.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
//通知t1继续执行
lock.notify();
}

}, "t2").start();

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}

new Thread(() -> {
System.out.println("t1启动");
synchronized(lock) {
for(int i=0; i<10; i++) {
c.add(new Object());
System.out.println("add " + i);

if(c.size() == 5) {
lock.notify();
//释放锁,让t2得以执行
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}

这里使用waitnotify做到,wait会释放锁,而notify不会释放锁,notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行,整个通信过程比较繁琐
下面这种方法使用Latch(门闩)替代wait notify来进行通知

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

// 添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
MyContainer c = new MyContainer();

CountDownLatch latch = new CountDownLatch(1);

new Thread(() -> {
System.out.println("t2启动");
if (c.size() != 5) {
try {
latch.await();

//也可以指定等待时间
//latch.await(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");

}, "t2").start();

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}

new Thread(() -> {
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);

if (c.size() == 5) {
// 打开门闩,让t2得以执行
latch.countDown();
}

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}, "t1").start();
}
}

好处是通信方式简单,同时也可以指定等待时间,使用awaitcountdown方法替代waitnotify,CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行,当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了,这时应该考虑countdownlatch/cyclicbarrier/semaphore.

-------------本文结束感谢您的阅读-------------
Thank you for your encouragement