线程同步工具类CountDownLatch

  • 1、CountDownLatch的作用
  • 2、CountDownLatch使用规则
  • 3、CountDownLatch的Demo
  • 4、CountDownLatch原理 (待补充)
  • 5、CountDownLatch的实现 (待补充)

1、CountDownLatch的作用

在JDK 5 以后sun公司引入了大量设计来解决并发的问题,学习使用这些类,我们可以更加自如的创建简单而健壮的并发程序,CountDownLatch类是用来同步一个或者多个任务的工具类,强制它们等待由其他任务执行的一组操作。

例如,现在有这样的一个需求,为了程序的快速运行,我们把一个程序分为三部分,分别是初始化、数据拉取、显示数据。初始化操作和数据拉取操作不会冲突,可以说这两个操作完全不相关,但是显示数据必须要在上面两个任务完成以后才可以继续。如果把这三个操作至于不同的线程,那么如果不对线程的同步加以控制的话,必将大乱,这个时候我们就可以先对CountDownLatct初始计数器为2,然后让初始化操作和数据拉取操作开始运行,然后在结束的时候调用countDown方法,然后显示操作在run开始的时候使用awit进行等待,直到计数器归0,显示操作开始执行,而这个时候上述两个操作都已经执行完毕。

2、CountDownLatch的使用规则

我们可以向CountDownLatch对象设置一个初始计数值,任何在CountDownLatch对象上调用wait的方法都将被阻塞,直到这个计数器达到0。其他任务在结束其工作时,可以在该对象上调用countDown()来减少这个计数器。CountDownLatch被设计为只触发一次,计数器不能被重置。

调用countDown的任务在产生这个调用时并没有被阻塞,只有对await的调用会产生阻塞,直到计数器达到0为止。

CountDownLatch的典型用法是将一个程序分为n个互相独立的可以解决的任务,并创建值为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown。等待问题被解决的任务在这个锁存器上面调用await,将它们自己揽住,直至锁存器计数结束,下面通过一个例子来演示该框架应该如何应用。

个人理解:CountDownLatch:我把他理解成倒计时锁
场景还原:一年级期末考试要开始了,监考老师发下去试卷,然后坐在讲台旁边玩着手机等待着学生答题,有的学生提前交了试卷,并约起打球了,等到最后一个学生交卷了,老师开始整理试卷,贴封条,下班,陪老婆孩子去了。
补充场景:我们在玩LOL英雄联盟时会出现十个人不同加载状态,但是最后一个人由于各种原因始终加载不了100%,于是游戏系统自动等待所有玩家的状态都准备好,才展现游戏画面。

3、CountDownLatch的Demo

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

/**
* 演示CountDownLatch的使用
* @author pengchengliu
*
*/
public class TestCountDownLatch {
static final int SIZE = 10 ;
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 0; i < 10; i++) {
exec.execute(new WaitingTask(countDownLatch));
}
for (int i = 0; i < SIZE; i++) {
exec.execute(new TaskProtion(countDownLatch));
}
System.out.println("Launch all task");
exec.shutdown();
}
}
class TaskProtion implements Runnable {
private static int sCounter = 0 ;
private final int id = sCounter ++ ;
private static Random sRandom = new Random(47) ;
private final CountDownLatch mLatch ;

public TaskProtion(CountDownLatch latch) {
this.mLatch = latch ;
}

@Override
public void run() {
try {
doWork();
mLatch.countDown();
} catch (InterruptedException e) {
System.out.println("TaskProtion is exiting form intrrupt");
}
}

public void doWork() throws InterruptedException {
TimeUnit.MICROSECONDS.sleep(sRandom.nextInt(2000));
System.out.println(this + " completed");
}

@Override
public String toString() {
return String.format("%1$-3d", id);
}
}
class WaitingTask implements Runnable {
private static int sCounter = 0 ;
private final int id = sCounter ++ ;
private final CountDownLatch mLatch ;

public WaitingTask (CountDownLatch latch) {
this.mLatch = latch ;
}

@Override
public void run() {
try {
this.mLatch.await();
System.out.println("Latch barrier passed for " + this);
} catch (InterruptedException e) {
System.out.println(this + " interrupted");
}
}

@Override
public String toString() {
return String.format("WaitingTask %1$-3d", id);
}
}
Launch all task
2 completed
8 completed
6 completed
5 completed
3 completed
9 completed
4 completed
0 completed
7 completed
1 completed
Latch barrier passed for WaitingTask 3
Latch barrier passed for WaitingTask 6
Latch barrier passed for WaitingTask 0
Latch barrier passed for WaitingTask 1
Latch barrier passed for WaitingTask 8
Latch barrier passed for WaitingTask 9
Latch barrier passed for WaitingTask 7
Latch barrier passed for WaitingTask 2
Latch barrier passed for WaitingTask 4
Latch barrier passed for WaitingTask 5

通过上述的任务输出,我们不难看出,我们将我们的任务很整齐的进行了先后顺序的控制,让我们程序当中有预支条件的任务可以在预支条件之后进行运行。这里就是通过CountDownLatch的锁存器当中的计数器来控制的。这里需要注意的两点就是,

  • CountDownLatch这个类当中的计数器的初始值,只能被初始化一次,不能初始化第二次,
  • 在使用的时候,必须多个对象同步同一个CountDownLatch这个类。

我们可以尝试将上述代码当中的CountDownLatch相关操作去除,我们再来看一下运行结果,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Launch all task
Latch barrier passed for WaitingTask 9
Latch barrier passed for WaitingTask 1
Latch barrier passed for WaitingTask 2
Latch barrier passed for WaitingTask 8
Latch barrier passed for WaitingTask 5
9 completed
2 completed
Latch barrier passed for WaitingTask 6
5 completed
8 completed
3 completed
Latch barrier passed for WaitingTask 3
1 completed
6 completed
Latch barrier passed for WaitingTask 4
7 completed
Latch barrier passed for WaitingTask 7
Latch barrier passed for WaitingTask 0
0 completed
4 completed

很明显结果杂乱无章,没有任何规律可言,到这里大家应该明白了CountDownLatch类的使用了吧。