一、了解CountDownLatch

CountDownLatchjava.util.concurrent下的一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。

CountDownLatch实现基于AQS,内有一个计数器,初始化CountDownLatch时必须调用构造函数CountDownLatch(int count)初始化计数值,线程调用countDown()对计数值减1,调用await()阻塞当前线程直到计数值为0. 也可设置线程阻塞超时时间await(long timeout, TimeUnit unit),超时后继续执行,不再阻塞线程。

二、用法实例

用法1:一个线程等待其他线程执行完成后再执行

场景:3人到饭店吃饭,各自前往。服务员等3人都到达后再开始上菜。

顾客类

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
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class CountDownLatchUseCase {
/*
模拟场景,等客人齐了上菜
*/
/*
顾客类
*/
private static class Customer implements Runnable {
private CountDownLatch latch;
private String name;

public Customer(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}

@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
try {
Random random = new Random();
System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
} catch (Exception e) {
e.printStackTrace();
}
latch.countDown(); // 执行完成
System.out.println(sdf.format(new Date()) + " " + name + "等待剩余人员:" + latch.getCount());
}
}

/*
服务员类
*/
private static class Waitress implements Runnable {
private CountDownLatch latch;
private String name;

public Waitress(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}

@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " " + name + "等待顾客");
latch.await();
System.out.println(sdf.format(new Date()) + " " + name + "开始上菜");
} catch (Exception e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);

new Thread(new Customer(latch, "张三")).start();
new Thread(new Customer(latch, "李四")).start();
new Thread(new Customer(latch, "王五")).start();

Thread.sleep(100);
new Thread(new Waitress(latch, "♥小芳♥")).start();
}
}
用法2 :设置超时时间

场景: 假如3个人去吃饭但是其中一个人发生意外(无法调用countDown())不去了,那服务员会一直等待不上菜,此时可以设置一个等待时间,超过这个时间不再等待。时间开发时需要注意线程阻塞问题。

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
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class CountDownLatchUseCase {
/*
模拟场景,等客人齐了上菜
*/
/*
顾客类
*/
private static class Customer implements Runnable {
private CountDownLatch latch;
private String name;

public Customer(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}

@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
try {
Random random = new Random();
System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
} catch (Exception e) {
e.printStackTrace();
}
if(name.equals("张三")) {
System.out.println("张三出车祸了");
return;
}
latch.countDown(); // 执行完成
System.out.println(sdf.format(new Date()) + " " + name + "等待剩余人员:" + latch.getCount());
}
}

/*
服务员类
*/
private static class Waitress implements Runnable {
private CountDownLatch latch;
private String name;

public Waitress(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}

@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " " + name + "等待顾客");
boolean result = latch.await(8, TimeUnit.SECONDS);
if(result) {
System.out.println(sdf.format(new Date()) + " " + name + ": 人到齐了 开始上菜");
} else {
System.out.println(sdf.format(new Date()) + " " + name + ": 时间到了 开始上菜");
}

} catch (Exception e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);

new Thread(new Customer(latch, "张三")).start();
//new Thread(new Customer(latch, "麻子")).start();
new Thread(new Customer(latch, "李四")).start();
new Thread(new Customer(latch, "王五")).start();

Thread.sleep(100);
new Thread(new Waitress(latch, "♥小芳♥")).start();
}
}
用法3:同时开始执行多个线程

场景: 赛跑,裁判发令后同时开始跑。所有人到达终点裁判统计成绩。

这里用到了两个CountDownLatch,一个是裁判,发令员控制计数,计数值初始化为1,当减到0,比赛者同时开始。

另一个是比赛者,比赛者控制计数,初始化为比赛者数量,每个比赛者到达终点时通知裁判统计成绩。类似于用法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
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
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

/**
* @author zhangqz
* @project study-prj
* @datetime 2020/12/7 11:09
*/
public class CountDownLatchUseCase3 {
private static class Runner implements Runnable {
private CountDownLatch latchRunner;
private CountDownLatch latchJudge;
private String name;

public Runner(CountDownLatch latchRunner, CountDownLatch latchJudge, String name) {
this.latchRunner = latchRunner;
this.latchJudge = latchJudge;
this.name = name;
}

@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
try {
Random random = new Random();
System.out.println(sdf.format(new Date()) + " " + name + "准备完毕...");
latchJudge.await();
System.out.println(sdf.format(new Date()) + " " + name + "出发...");
Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(sdf.format(new Date()) + " " + name + "到达终点!");
latchRunner.countDown();
}
}

public static void main(String[] args) {
CountDownLatch latchRunner = new CountDownLatch(3);
CountDownLatch latchJudge = new CountDownLatch(1);
System.out.println("各就各位!");
new Thread(new CountDownLatchUseCase3.Runner(latchRunner, latchJudge, "张三")).start();
new Thread(new CountDownLatchUseCase3.Runner(latchRunner, latchJudge,"李四")).start();
new Thread(new CountDownLatchUseCase3.Runner(latchRunner, latchJudge,"王五")).start();

try {
Thread.sleep(4 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 裁判发令 3个runner开始跑
System.out.println("开始");
latchJudge.countDown();

try {
// 裁判等待runner跑完
latchRunner.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("比赛结束");
}

}

三、CountDownLatch的不足

CountDownLatch是一次性的,计数值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。