CountDownLatch,顾名思义就是一个倒计时器。(其实Latch的意思是门闩,这个词的本意是不断的计数减一,减到0了就打开门闩放行,但通常我们还是叫它倒计时器)
这个倒计时器和我们传统意义上的倒计时器并不完全一样,这个倒计时器的意思是,一开始规定几个线程(比如说我们这里一开始有10个线程),那么每个线程结束之后,会调用倒计时器实例对象的方法,让它的“计数器”减一,当计数器减到0时,门闩打开放行。
CountDownLatch的简单原理和应用 :
在主线程调用CountDownLatch的实例方法 await() 就可以使主线程阻塞起来,通过子线程调用实例方法 countdown() ,使计数器减1,直到倒计时器减到0之后,门闩打开,主线程可以继续执行。
简单来说就是主线程得等所有的子线程都执行完了之后才能执行,这是一个很有用的线程。
对应生活中的场景,我想到《爱情公寓》中有一集,是胡一菲要帮她老弟展博和宛瑜拍婚纱照,一灰同志就如战场指挥官一般分派它的好友们去处理各种事情,灯光,摄像,摄影棚,头纱,戒指等等,假如把一灰同志看成是主线程,其余的好友看成是多个子线程的话,那么一灰同志就得等他们所有的线程都执行完任务之后才能振臂一呼:“开拍”。(虽然最后基本上都没搞定。。)
ps:什么,你不知道一灰同志是谁?----- 胡一灰啦
什么,你说拿情景喜剧里面的片段来举例子简直是扯淡,好吧,那就再举一个实际点的例子,比如说我现在在写的一个多线程的小例子,使用多线程下载图片(不一定是图片,也可以是其他的资源),思路很简单,就是先获取目标文件的长度,分成几块,每个线程下一块,都下载好了之后就合并。
那么这里面有一个关键的问题就是如何才能让主线程等待所有的子线程都下载好了之后再进行合并,CountDownLatch则正好对应了这样的场景。
实例代码嘛,还是直接用的书上的例子吧(是一个发射火箭的例子,线程池中的每一个线程代表火箭发射前的一项技术检查,但是作者偷懒,就用一个for循环代替了,嘻嘻,我也偷下懒):
import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 演示倒计时器的简单使用 */ public class Lesson20_CountDownLatch implements Runnable{ static final CountDownLatch end = new CountDownLatch(10); static final Lesson20_CountDownLatch demo = new Lesson20_CountDownLatch(); @Override public void run() { try { //模拟检查任务 Thread.sleep(new Random().nextInt(10)*1000); System.out.println("check complete"); //每完成一个线程,计数器就减一 end.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(10); for (int i = 0; i < 10 ; i++) { exec.submit(demo); } //阻塞主线程,等待检查 try { end.await(); } catch (InterruptedException e) { e.printStackTrace(); } //发射火箭 System.out.println("Fire!"); exec.shutdown(); } }
听说倒计时器不单单可以阻塞一个线程,还可以阻塞一组线程,大家可以试试。
CyclicBarrier (翻译过来好像是循环栅栏)
CyclicBarrier和CountDownLatch的作用很相似,但个人感觉还是有所区别的。CyclicBarrier也实现了线程间的相互等待,但是并没有阻塞主线程。
还是上面那个拍婚纱的例子,在上面那个例子中,一灰同志要等所有人都把任务完成才能帮他老弟和宛瑜拍婚纱,其实可以有两种办法实现:
a. 一灰知道她一共分派了5个任务,每有一个人来报道,她就知道解决了一个,还剩4个任务,当所有人都一 一回来之后,她就知道任务都搞定了,这是倒计时器的做法,请注意这个时候主线程是被阻塞住的,也就是说一灰这时啥也不能干,眨下眼睛都不行。你可以认为她睡着了。
b. 一灰跟他们所有人说,你们全部都搞定了再一起回来,否则(此时只见一灰单手将不锈钢勺子给掰弯了),大家见状顿时四散奔逃。。
于是当悠悠和关谷搞定了摄影师之后便赶快打电话给子乔,“子乔,你头纱搞到没”,“早就搞好了,我把我前女友叫过来了,她今天结婚”(只见一抹鲜红的巴掌印留在了子乔的脸上),咳咳。。 好的,就这样他们一群人相互联系,确认所有人都搞定了自己的任务,这才回去面见陛下,哦不,是我们的指挥官一灰同志。
这里注意,此时,一灰是可以干任何事情的,该吃手抓饼吃手抓饼,该喝茶喝茶,时不时还能打个电话催一催进度。
那么,问题来了,我们这里是用一灰来模拟的主线程,那么他们还没回来,一灰就开始带着他老弟去拍婚纱了怎么办。
所以说,拍婚纱这个行为不是由主线程控制的。我们可以看一下CyclicBarrier的构造函数,里面有这么一条:
CyclicBarrier(int parties, Runnable barrierAction)
这里后面这个barrierAction就对应了拍婚纱这个行为。
那循环栅栏中的循环是什么意思呢?
这就是它和倒计时器的另外一个区别了,倒计时器结束了,门闩打开了就关不上了,而循环栅栏则可以重复使用,比如一灰让他们先去买10台豆浆机,再去买10台面包机,先后顺序不能反,于是他们会相互联系确认都买了豆浆机,然后吃个饭庆祝下,哦不是,然后打电话给一灰汇报下,然后再各自去买面包机,再相互联系,是否每个人都买了,确认之后,再吃个饭庆祝一下。。(注意,这里的打电话,吃饭庆祝等行为都是模拟的barrierAction)
- CyclicBarrier的简单原理和应用:
CyclicBarrier与CountDownLatch不同,并不是主线程拿着一个计时器,而是每个子线程都持有一个CyclicBarrier的实例,如果有5个线程持有了相同的CyclicBarrier的实例对象,那么这5个线程就都得确保其他线程搞定了,我才能继续执行,上面解释过,这里就不解释了
偷懒如我,还是用的书上的例子(什么,让我实现自己举的爱情公寓的例子,什么,你说啥,我这信号不好,听不清了,拜拜):
package thread.thread_util; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * 循环栅栏 */ public class Lesson19_CyclicBarrier { public static class Soldier implements Runnable { private String soldier; private final CyclicBarrier cyclic; Soldier(CyclicBarrier cyclic,String soldierName) { this.cyclic = cyclic; this.soldier = soldierName; } @Override public void run() { //这里为了展示栅栏是可以重复使用的,所以使用了2次await try { //等到所有士兵到齐 cyclic.await(); dowork(); //等待所有士兵完成工作 cyclic.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } void dowork() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("soldier" + ": 任务完成"); } } //线程完毕时执行的动作 public static class BarrierRun implements Runnable { boolean flag; int N; public BarrierRun(boolean flagk, int N) { this.flag = flag; this.N = N; } @Override public void run () { if(flag) { System.out.println(String.format("司令:【士兵%d个,任务完成】",N)); } else { System.out.println(String.format("司令:【士兵%d个,集合完毕】",N)); flag = true; } } } public static void main(String args[]) throws InterruptedException{ final int N = 5; boolean flag = false; CyclicBarrier cyclic = new CyclicBarrier(N,new BarrierRun(flag,N)); System.out.println("集合队伍"); for (int i = 0; i < 5 ; i++) { System.out.println("士兵" + i + "报道"); new Thread(new Soldier(cyclic,"士兵" + (i + 1))).start(); } // System.out.println("主线程over"); 你可以试试,这里主线程不会被阻塞的 } }
关于这两个同步器的使用就告一段落了,若有错漏之处,请在评论区指出。