CountDownLatch和CyclicBarrier的简单使用

简介

CountDownLatch和CyclicBarrier都是线程同步的辅助工具。

CountDownLatch

CountDownLatch可以想象成一个上了N把锁的门栓。当某个线程调用await方法时,它会去检测当前门栓上是否有锁,如果有锁的话就继续执行,否则就继续等待,知道全部的锁都被解除。可以通过调用countDown方法来从门栓上解除一把锁。

有一个十分典型的应用场景就是利用CountDownLatch来保证线程同时开始执行或者保证当前线程等待其他线程全部结束之后继续执行,代码如下:

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
public class TestDemo {
public static void main(String args[]) throws InterruptedException {
int threadCount = 10;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
// create and start threads
new Thread(new Worker(startSignal, doneSignal, "th-" + i)).start();
}
System.out.println("sub threads waiting for main thread");
startSignal.countDown(); // let all threads proceed
doneSignal.await(); // wait for all to finish
System.out.println("sub threads complete");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final String name;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal, String name) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.name = name;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
}
catch (InterruptedException ex) {
} // return;
}
void doWork() {
System.out.println(this.name + " is doing work");
}
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
sub threads waiting for main thread
th-0 is doing work
th-3 is doing work
th-4 is doing work
th-1 is doing work
th-2 is doing work
th-9 is doing work
th-8 is doing work
th-7 is doing work
th-5 is doing work
th-6 is doing work
sub threads complete

CyclicBarrier

我们可以用学校春游之前在操场集合的这个场景来理解CyclicBarrier。一个班级如果要出发,那么必须要等班里所有的人都到了才能走。到了集合地点的人需要签到,然后等待其他人,这个过程就像CyclicBarrier中的await方法。当所有人都签到完毕,班主任会上前说一些注意事项,这一步就像下面代码中的barrierAction。然后,所有人才可以出发,在代码中的表现就是继续执行await之后的方法。示例代码如下:

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
public class TestDemo {
public static void main(String args[]) throws InterruptedException {
int threadCount = 10;
Runnable barrierAction = new Runnable() {
public void run() {
System.out.println("sub threads complete");
}
};
CyclicBarrier barrier = new CyclicBarrier(threadCount, barrierAction);
List<Thread> threads = new ArrayList<>(threadCount);
for (int i = 0; i < threadCount; i++) {
Thread thread = new Thread(new Worker(barrier, "th-" + i));
threads.add(thread);
thread.start();
}
// wait until done
for (Thread thread : threads) {
thread.join();
}
}
public static class Worker implements Runnable {
private CyclicBarrier barrier;
private String name;
Worker(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
public void run() {
try {
doWork();
barrier.await();
System.out.println(name + " waiting finish");
}
catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
private void doWork() {
System.out.println(name + " is doing work");
}
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
th-0 is doing work
th-1 is doing work
th-2 is doing work
th-3 is doing work
th-4 is doing work
th-5 is doing work
th-6 is doing work
th-7 is doing work
th-8 is doing work
th-9 is doing work
sub threads complete
th-9 waiting finish
th-0 waiting finish
th-1 waiting finish
th-2 waiting finish
th-4 waiting finish
th-3 waiting finish
th-5 waiting finish
th-7 waiting finish
th-6 waiting finish
th-8 waiting finish

区别

这两个类,在定义上的区别似乎不大。一个是latch,一个是barrier,都体现了阻塞的意思,并且调用await的时候都会阻塞。但是他们阻塞时的线程的关系是不一样的。对于CountDownLatch来说,是一个或多个线程在等待一个或多个线程,他们的等待关系从宏观的角度来看只有一组。而对于CyclicBarrier来说,是所有的线程在互相等待,他们的等待关系是交叉的。

另外一个区别是,CyclicBarrier的barrier是可以复用的(调用reset方法),但是CountDownLatch的latch只能用一次。这个区别在实际的业务当中会产生一定的影响。

坚持原创技术分享,您的支持将鼓励我继续创作!