7 CountDownLatch/Semaphore/CyclicBarrier
countdownlatch
countdownlatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到 countdown 是倒数的意思,类似于我们倒计时的概念。countdownlatch 提供了两个方法,一个是 countDown,一个是 await,countdownlatch 初始化的时候需要传入一个整数,在这个整数倒数到 0 之前,调用了 await 方法的程序都必须要等待,然后通过 countDown 来倒数。
public class CountDownLatchDemo extends Thread{ static CountDownLatch countDownLatch=new CountDownLatch(100); public static void main(String[] args) { for(int i=0;i<3;i++){ new CountDownLatchDemo().start(); } countDownLatch.countDown();//倒计数 } @Override public void run() { try { countDownLatch.await(); //阻塞 3个线程 Thread.currentThread 阻塞当前执行的线程,让当前线程等待。 } catch (InterruptedException e) { e.printStackTrace(); } //TODO 执行代码 System.out.println("ThreadName:"+Thread.currentThread().getName()); } }
从代码的实现来看,有点类似 join 的功能,但是比 join 更加灵活。CountDownLatch 构造函数会接收一个 int 类型的参数作为计数器的初始值,当调用 CountDownLatch 的countDown 方法时,这个计数器就会减一。通过 await 方法去阻塞去阻塞主流程.
总的来说,凡事涉及到需要指定某个人物在执行之前,要等到前置任务执行完毕之后才执行的场景,都可以使用CountDownLatch.
CountDownLatch.await : countDown() 方法每次调用都会将 state 减 1,直到state 的值为 0;而 CountDownLatch.await 是一个阻塞方法,当 state 减为 0 的时候,await 方法才会返回。await 可以被多个线程调用,大家在这个时候脑子里要有个图:所有调用了await 方法的线程阻塞在 AQS 的阻塞队列中,等待条件满足(state == 0),将线程从队列中一个个唤醒过来。
CountDownLatch.countDown 由于线程被 await 方法阻塞了,所以只有等到countdown 方法使得 state=0 的时候才会被唤醒,我们来看看 countdown 做了什么
1. 只有当 state 减为 0 的时候,tryReleaseShared 才返回 true, 否则只是简单的 state = state - 1
2. 如果 state=0, 则调用 doReleaseShared唤醒处于 await 状态下的线程
Semaphore [ˈseməfɔːr] 信号灯
semaphore 也就是我们常说的信号灯,semaphore 可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流的作用。
叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。
public class SemaphoreDemo { //限流(AQS) //permits; 令牌(5) //公平和非公平 static class Car extends Thread{ private int num; private Semaphore semaphore; public Car(int num, Semaphore semaphore) { this.num = num; this.semaphore = semaphore; } public void run(){ try { semaphore.acquire(); //获得一个令牌, 如果拿不到令牌,就会阻塞 System.out.println("第"+num+" 抢占一个车位"); Thread.sleep(2000); System.out.println("第"+num+" 开走喽"); semaphore.release();//释放令牌,循环使用 } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Semaphore semaphore=new Semaphore(5); //5个车位 for(int i=0;i<10;i++){ //10辆车 new Car(i,semaphore).start(); } } }
第0 抢占一个车位
第3 抢占一个车位
第2 抢占一个车位
第4 抢占一个车位
第5 抢占一个车位
第3 开走喽
第4 开走喽
第1 抢占一个车位
第0 开走喽
第6 抢占一个车位
第7 抢占一个车位
第2 开走喽
第8 抢占一个车位
第5 开走喽
第9 抢占一个车位
第1 开走喽
第8 开走喽
第9 开走喽
第6 开走喽
第7 开走喽
补充:
Semaphore 分公平策略和非公平策略,
区别就在于是不是会先判断是否有线程在排队,然后才进行 CAS 减操作,通过对比发现公平和非公平的区别就在于是否多了一个hasQueuedPredecessors 的判断
CyclicBarrier
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。
CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 当前线程已经到达了屏障,然后当前线程被阻塞。
使用场景:当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用 CyclicBarrier
public class DataImportThread extends Thread{ private CyclicBarrier cyclicBarrier; private String path; public DataImportThread(CyclicBarrier cyclicBarrier, String path) { this.cyclicBarrier = cyclicBarrier; this.path = path; } @Override public void run() { System.out.println("开始导入:"+path+" 数据"); //TODO try { cyclicBarrier.await(); //阻塞 condition.await() } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }
public class CycliBarrierDemo extends Thread{ @Override public void run() { System.out.println("开始进行数据分析"); } //循环屏障 //可以使得一组线程达到一个同步点之前阻塞. public static void main(String[] args) { CyclicBarrier cyclicBarrier=new CyclicBarrier (3,new CycliBarrierDemo()); new Thread(new DataImportThread(cyclicBarrier,"file1")).start(); new Thread(new DataImportThread(cyclicBarrier,"file2")).start(); new Thread(new DataImportThread(cyclicBarrier,"file3")).start(); } }
开始导入:file1 数据
开始导入:file2 数据
开始导入:file3 数据
开始进行数据分析
注意点
1)对于指定计数值 parties,若由于某种原因,没有足够的线程调用 CyclicBarrier 的 await,则所有调用 await 的线程都会被阻塞;
2)同样的 CyclicBarrier 也可以调用 await(timeout, unit),设置超时时间,在设定时间内,如果没有足够线程到达,则解除阻塞状态,继续工作;
3)通过 reset 重置计数,会使得进入 await 的线程出现BrokenBarrierException;
4 )如果采用是 CyclicBarrier(int parties, RunnablebarrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程
实现原理
CyclicBarrier 相比 CountDownLatch 来说,要简单很多,源码实现是基于 ReentrantLock 和 Condition 的组合使用。看如下示意图,CyclicBarrier 和 CountDownLatch 是不是很像,只是 CyclicBarrier 可以有不止一个栅栏,因为它的栅栏(Barrier)可以重复使用(Cyclic)

浙公网安备 33010602011771号