这个东西第一次看到是在一场校招笔试上看到的,笔试其他题目做的还好,看的这个的时候一脸懵逼。一看就知道这两个词是关于多线程的,因为也就多线程会起这么sao的名字了,但是我比他更sao,因为这道题我空着没做。虽然这个没做出来,但是其他做的挺好的,至今还没收到面试邀约,伤心。
1.CountDownLatch减计数,CyclicBarrier加计数。
2.CountDownLatch是一次性的,CyclicBarrier可以重用。
3.CountDownLatch和CyclicBarrier都是java.util.concurrent包下面的多线程工具类。
4.从字面上理解,CountDown表示减法计数,Latch表示门闩的意思,计数为0的时候就可以打开门闩了。Cyclic Barrier表示循环的障碍物。两个类都含有这一个意思:对应的线程都完成工作之后再进行下一步动作,也就是大家都准备好之后再进行下一步。然而两者最大的区别是,进行下一步动作的动作实施者是不一样的。这里的“动作实施者”有两种,一种是主线程(即执行main函数),另一种是执行任务的其他线程,后面叫这种线程为“其他线程”,区分于主线程。对于CountDownLatch,当计数为0的时候,下一步的动作实施者是main函数;对于CyclicBarrier,下一步动作实施者是“其他线程”。
一、CountDownLatch
一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。
用法:用给定的计数初始化CountDownLath。调用countDown()方法计数减 1,在计数被减到 0之前,调用await方法会一直阻塞。减为 0之后,则会迅速释放所有阻塞等待的线程,并且调用await操作会立即返回。
ps:CountDownLath计数无法被重置,如果需要重置计数,请考虑使用CyclicBarrier.
对于CountDownLatch,其他线程为游戏玩家,比如王者荣耀,比如仅仅控制开始游戏,主线程为控制游戏开始的线程。在所有的玩家都准备好之前,主线程是处于等待状态的,也就是游戏不能开始。当所有的玩家准备好之后,下一步的动作实施者为主线程,即开始游戏。开始游戏后就结束了,所以CountDownLatch只能用一次。
我们使用代码模拟这个过程,我们模拟了三个玩家,在三个玩家都准备好之后,游戏才能开始。
/**
* @author: hyl
* @date: 2019/09/26
**/
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(4);
for (int i = 0; i < latch.getCount(); i++) {
new Thread(new MyThread(latch) , "play" + i).start();
}
System.out.println("所有等待所有玩家!");
latch.await();
System.out.println("开始游戏");
}
private static class MyThread implements Runnable{
private CountDownLatch latch;
public MyThread(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
Random random = new Random();
//随机生成1000-3000的整数
int randomNum = random.nextInt(2000 + 1) + 1000;
Thread.sleep(randomNum);
System.out.println(Thread.currentThread().getName() +
"准备所用的时间为 : " + (randomNum *1.0 / 1000) + "s");
latch.countDown();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
代码运行结果:
等待所有玩家!
play0准备所用的时间为 : 1.531s
play2准备所用的时间为 : 2.518s
play1准备所用的时间为 : 2.59s
play3准备所用的时间为 : 2.837s
开始游戏
CountDownLatch强调的是一个线程(或多个)需要等待另外的n个线程干完某件事情之后才能继续执行。 上述例子,main线程是系统,4个线程是游戏玩家的。游戏玩家分别进行准备。4个人谁准备好了,countdown一下,直到4个人全部到达。
二、CyclicBarrier
定义:是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共的屏障点,所有线程一起继续执行或者返回。一个特性就是CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程到达之后,该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新此共享状态,此屏障操作很有用。
用法:用计数 N 初始化CyclicBarrier, 每调用一次await,线程阻塞,并且计数+1(计数起始是0),当计数增长到指定计数N时,所有阻塞线程会被唤醒。继续调用await也将迅速返回。
对于CyclicBarrier,假设有一家公司要全体员工进行团建活动,活动内容为翻越三个障碍物,每一个人翻越障碍物所用的时间是不一样的。但是公司要求所有人在翻越当前障碍物之后再开始翻越下一个障碍物,只有当所有人翻越第一个障碍物之后,才开始翻越第二个,如果有一个员工没翻过,其他员工都要等,以此类推。类比地,每一个员工都是一个“其他线程”。
我们使用代码来模拟上面的过程。我们设置了四个员工和三个障碍物。可以看到所有的员工翻越了第一个障碍物之后才开始翻越第二个的。
/**
* @author: hyl
* @date: 2019/09/26
**/
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(4);
for (int i = 0; i < barrier.getParties(); i++) {
new Thread(new MyThread(barrier) , "员工" + i).start();
}
System.out.println("Main Thread is dead!");
}
private static class MyThread implements Runnable{
private CyclicBarrier barrier;
public MyThread(CyclicBarrier barrier){
this.barrier = barrier;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try{
Random random = new Random();
//随机生成1000-3000的整数
int randomNum = random.nextInt(2000 + 1) + 1000;
Thread.sleep(randomNum);
System.out.println(Thread.currentThread().getName() +
"通过了第" + i + "个障碍物,所用的时间为 : " + (randomNum *1.0 / 1000) + "s");
barrier.await();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
运行结果:
Main Thread is dead!
员工1通过了第0个障碍物,所用的时间为 : 1.089s
员工3通过了第0个障碍物,所用的时间为 : 1.379s
员工2通过了第0个障碍物,所用的时间为 : 2.536s
员工0通过了第0个障碍物,所用的时间为 : 2.805s
员工1通过了第1个障碍物,所用的时间为 : 1.346s
员工3通过了第1个障碍物,所用的时间为 : 1.487s
员工2通过了第1个障碍物,所用的时间为 : 2.05s
员工0通过了第1个障碍物,所用的时间为 : 2.353s
员工1通过了第2个障碍物,所用的时间为 : 1.178s
员工0通过了第2个障碍物,所用的时间为 : 2.416s
员工2通过了第2个障碍物,所用的时间为 : 2.567s
员工3通过了第2个障碍物,所用的时间为 : 2.692s
CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。正如上例,只有5个人全部跑到终点,大家才能接着跑,否则只能全等着。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
文章为DavidHan原创,如果文章有错的地方欢迎指正,大家互相交流。