CyclicBarrier和CountDownLatch的简单使用

### 前言
>在java.util.concurrent包下有CountDownLatch和CyclicBarrier两个用于完成"等待"的类,他们的功能看起来很相似,接下来简单的使用和区分二者

### CountDownLatch

从字面上理解,CountDown表示减法计数,Latch表示门闩的意思,计数为0的时候就可以打开门闩了。
以下来自《java编程思想(第四版)》:
>可以用来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用await()的方法都将阻塞,直至这个计数值达到0.其他任务在结束其工作时,可以在该对象上调用countDown()来减少这个计数值。CountDownLatch被设计为只触发一次,计数值不能被重置。如果你需要能够重置计数值的版本,可以使用CyclicBarrier。
CountDownLatch的典型做法是将一个程序分为n个互相独立可解决任务,并创建值为n的CountDownLatch。每当任务完成时,会在这个锁存器上调用countDown()。等待问题被解决的任务在这个锁存器上调用await(),将他们自己拦住,知道锁存器计数结束。

#### 示例代码
```java
package pers.mine.scratchpad.concurrent;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch简单使用
 */
public class CountDownLatchTest {
	public static void main(String[] args) {
		final CountDownLatch cl = new CountDownLatch(3);

		// 需要等待线程
		new Thread(() -> {
			try {
				long start = System.currentTimeMillis();
				System.out.println(start + ":" + Thread.currentThread().getName() + " 启动,等待其他线程");
				cl.await();
				long end = System.currentTimeMillis();
				System.out.println(end + ":" + Thread.currentThread().getName() + " 执行结束,等待时间:"+(end-start)+"ms");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
		new Thread(() -> {
			try {
				long start = System.currentTimeMillis();
				System.out.println(start + ":" + Thread.currentThread().getName() + " 启动,等待其他线程");
				cl.await();
				long end = System.currentTimeMillis();
				System.out.println(end + ":" + Thread.currentThread().getName() + " 执行结束,等待时间:"+(end-start)+"ms");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();

		// 被等待线程
		new Thread(() -> {
			try {
				int tmp = (int) (Math.random() * 3000);
				System.out.println(
						System.currentTimeMillis() + ":" + Thread.currentThread().getName() + " 启动,预计执行" + tmp + "ms");
				Thread.sleep(tmp);
				System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "结束");
				cl.countDown();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
		new Thread(() -> {
			try {
				int tmp = (int) (Math.random() * 3000);
				System.out.println(
						System.currentTimeMillis() + ":" + Thread.currentThread().getName() + " 启动,预计执行" + tmp + "ms");
				Thread.sleep(tmp);
				System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "结束");
				cl.countDown();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
		new Thread(() -> {
			try {
				int tmp = (int) (Math.random() * 3000);
				System.out.println(
						System.currentTimeMillis() + ":" + Thread.currentThread().getName() + " 启动,预计执行" + tmp + "ms");
				Thread.sleep(tmp);
				System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "结束");
				cl.countDown();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();
	}
}
```
#### 代码执行结果:
```
1561549571385:Thread-0 启动,等待其他线程
1561549571385:Thread-1 启动,等待其他线程
1561549571385:Thread-2 启动,预计执行2575ms
1561549571385:Thread-3 启动,预计执行711ms
1561549571385:Thread-4 启动,预计执行153ms
1561549571543:Thread-4结束
1561549572101:Thread-3结束
1561549573965:Thread-2结束
1561549573965:Thread-0 执行结束,等待时间:2580ms
1561549573965:Thread-1 执行结束,等待时间:2580ms

```
#### 结果分析
>通过打印的时间可以看出,Thread-0,和Thread-1在启动后被阻塞,等Thread-2,Thread-3,Thread-4中执行时间最久的Thread-2执行结束后才继续执行

### CyclicBarrier

从字面上理解,Cyclic Barrier表示循环的障碍物。
以下同来自《java编程思想(第四版)》:
>CyclicBarrier适用于这样的情况,你希望创建一组任务,他们并行的执行工作,然后在进行下一步骤前等待,直至所有任务都完成(看起来有点像join())。它使得所有的并行任务都将在栅栏处列队,因此可以一致的向前移动。这非常像CountDownLatch,只是CountDownLatch是只触发一次,而CyclicBarrier可以通过reset()实现重用。

#### 示例代码
```java
package pers.mine.scratchpad.concurrent;

import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CyclicBarrier;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

/**
 * 模拟选举 10 个人选ABC中一个,当所有人投完票后打印票数,以及胜利者
 */
public class CyclicBarrierTest {
	static  final List<String> voteList = new CopyOnWriteArrayList<String>();// 票箱
	static String[] campaigners = { "A", "B", "C" };
	static String winner = "";
	public static void main(String[] args) throws InterruptedException {
		CyclicBarrier cb = new CyclicBarrier(10,()->{
			Multimap<String, String> voteMap = ArrayListMultimap.create();
			System.out.println(System.currentTimeMillis() + ":所有人投票结束,开始统计票数");
			for (String string : voteList) {
				voteMap.put(string, "0");
			}
			int max = 0;
			for (String key : voteMap.keySet()) {
				int size = voteMap.get(key).size();
				System.out.println(String.format("%s 的票为 %s", key,size));//得票为0的未打印
				if(size > max) {
					max = size;
					winner = key; //存在相等的,可能不准确
				}
			}
			System.out.println(String.format("%s 的票为 胜利者", winner));
		}) ;
		
		System.out.println("第一次投票:");
		for (int i = 0; i < 10; i++) {
			Thread th = new Thread(new CBRunnable(cb,""+i));
			th.start();
		}
		
		Thread.sleep(5000);
		System.out.println("\n第二次投票:");
		cb.reset();
		voteList.clear();
		for (int i = 0; i < 10; i++) {
			Thread th = new Thread(new CBRunnable(cb,""+i));
			th.start();
		}
		
	}

	/**
	 * 投票人
	 */
	static class CBRunnable implements Runnable {
		CyclicBarrier cb = null;
		String name = "";

		public CBRunnable(CyclicBarrier cb, String name) {
			this.cb = cb;
			this.name = name;
		}

		public void run() {
			try {
				long start = System.currentTimeMillis();
				int waitTime = (int) (Math.random() * 3000);
				System.out.println(start + ":" + name + " 开始思考选谁");
				Thread.sleep(waitTime);
				String target = campaigners[waitTime % 3];
				voteList.add(target);
				System.out.println(start + ":" + name + " 选择了 "+target+",等待统计投票结果");
				cb.await();
				System.out.println(start + ":" + name + " 知道了 "+winner+" 是胜利者");
			} catch (InterruptedException | BrokenBarrierException e) {
				e.printStackTrace();
			}
		}

	}
}
```
#### 代码执行结果:
```
第一次投票:
1561555925334:1 开始思考选谁
1561555925334:0 开始思考选谁
1561555925334:9 开始思考选谁
1561555925334:2 开始思考选谁
1561555925334:7 开始思考选谁
1561555925334:8 开始思考选谁
1561555925334:5 开始思考选谁
1561555925334:4 开始思考选谁
1561555925334:6 开始思考选谁
1561555925334:3 开始思考选谁
1561555925334:4 选择了 A,等待统计投票结果
1561555925334:5 选择了 C,等待统计投票结果
1561555925334:6 选择了 B,等待统计投票结果
1561555925334:8 选择了 C,等待统计投票结果
1561555925334:2 选择了 C,等待统计投票结果
1561555925334:9 选择了 A,等待统计投票结果
1561555925334:3 选择了 C,等待统计投票结果
1561555925334:1 选择了 B,等待统计投票结果
1561555925334:7 选择了 C,等待统计投票结果
1561555925334:0 选择了 A,等待统计投票结果
1561555928329:所有人投票结束,开始统计票数
A 的票为 3
B 的票为 2
C 的票为 5
C 的票为 胜利者
1561555925334:0 知道了 C 是胜利者
1561555925334:6 知道了 C 是胜利者
1561555925334:5 知道了 C 是胜利者
1561555925334:4 知道了 C 是胜利者
1561555925334:7 知道了 C 是胜利者
1561555925334:1 知道了 C 是胜利者
1561555925334:3 知道了 C 是胜利者
1561555925334:9 知道了 C 是胜利者
1561555925334:2 知道了 C 是胜利者
1561555925334:8 知道了 C 是胜利者

第二次投票:
1561555930346:0 开始思考选谁
1561555930346:1 开始思考选谁
1561555930346:2 开始思考选谁
1561555930346:3 开始思考选谁
1561555930346:4 开始思考选谁
1561555930346:5 开始思考选谁
1561555930346:6 开始思考选谁
1561555930346:7 开始思考选谁
1561555930346:9 开始思考选谁
1561555930346:8 开始思考选谁
1561555930346:6 选择了 A,等待统计投票结果
1561555930346:3 选择了 A,等待统计投票结果
1561555930346:4 选择了 C,等待统计投票结果
1561555930346:7 选择了 B,等待统计投票结果
1561555930346:9 选择了 A,等待统计投票结果
1561555930346:0 选择了 B,等待统计投票结果
1561555930346:5 选择了 A,等待统计投票结果
1561555930346:8 选择了 A,等待统计投票结果
1561555930346:1 选择了 A,等待统计投票结果
1561555930346:2 选择了 C,等待统计投票结果
1561555932487:所有人投票结束,开始统计票数
A 的票为 6
B 的票为 2
C 的票为 2
A 的票为 胜利者
1561555930346:2 知道了 A 是胜利者
1561555930346:1 知道了 A 是胜利者
1561555930346:8 知道了 A 是胜利者
1561555930346:5 知道了 A 是胜利者
1561555930346:0 知道了 A 是胜利者
1561555930346:7 知道了 A 是胜利者
1561555930346:9 知道了 A 是胜利者
1561555930346:4 知道了 A 是胜利者
1561555930346:3 知道了 A 是胜利者
1561555930346:6 知道了 A 是胜利者
```
#### 结果分析
> 通过打印结果,第一次投票最后做出选择的0选择完和第二次投票最后做出选择的7选择完后,统计方法才开始统计结果,且在统计完成后,各线程会继续执行直到结束

### 二者的区别
一些简单场景,如控制一组线程同时执行,两个类都可以完成,但CountDownLatch的等待线程和被等待线程是分开的,不可重复执行。CyclicBarrier中各线程既是被等待线程,又是等待线程,同时还可以在同步执行前统一执行一个Runnable(如上述示例的统计方法),且CyclicBarrier可以通过重置来重复使用

### 代码地址
[GitHub](https://github.com/yebukong/scratchpad/tree/master/src/test/java/pers/mine/scratchpad/concurrent "GitHub")
[Gitee](https://gitee.com/yebukong/scratchpad/tree/80734afbd7a41cd40e8141f85e4588101672fe46/src/test/java/pers/mine/scratchpad/concurrent "Gitee")
### 参考
[https://blog.csdn.net/liangyihuai/article/details/83106584](https://blog.csdn.net/liangyihuai/article/details/83106584 "https://blog.csdn.net/liangyihuai/article/details/83106584")

------------
> 本文由 [叶不空](https://yebukong.com "叶不空") 创作,采用 [知识共享署名 4.0 国际许可协议](https://creativecommons.org/licenses/by/4.0/ "知识共享署名 4.0 国际许可协议")进行许可,转载请附上链接!
> 本文链接: [https://yebukong.com/article/1110234193767530498.html](https://yebukong.com/article/1110234193767530498.html "CyclicBarrier和CountDownLatch的简单使用")
                        
(°ο°)评论插件未能完成加载!