【并发编程系列7】CountDownLatch,CyclicBarrier,Semaphore实现原理分析

==========================================================================

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一 组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会 开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景

CyclicBarrier使用示例1


package com.zwx.concurrent.jucUtil;

import java.util.concurrent.CyclicBarrier;

public class CyclicbarrierDemo {

static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);

public static void main(String[] args) {

new Thread(()-> {

try {

cyclicBarrier.await();

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(“我是线程t1”);

},“t1”).start();

new Thread(()-> {

try {

cyclicBarrier.await();

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(“我是线程t2”);

},“t2”).start();

System.out.println(“主线程==end”);

}

}

输出结果:

在这里插入图片描述

这个t1和t2的输出结果是随机的。

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrier- Action),用于在线程到达屏障时,优先执行barrierAction。

CyclicBarrier使用示例2


package com.zwx.concurrent.jucUtil;

import java.util.concurrent.CyclicBarrier;

public class CyclicbarrierDemo2 {

static CyclicBarrier cyclicBarrier = new CyclicBarrier(2,new MyThread());

public static void main(String[] args) {

new Thread(()-> {

try {

cyclicBarrier.await();

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(“我是线程t1”);

},“t1”).start();

new Thread(()-> {

try {

cyclicBarrier.await();

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(“我是线程t2”);

},“t2”).start();

System.out.println(“主线程==end”);

}

static class MyThread extends Thread {

@Override

public void run() {

System.out.println(“do something”);

}

}

}

输出结果:

在这里插入图片描述

我们可以看到,在线程t1和t2输出前会先输出自定义线程的信息。

CyclicBarrier实现原理


CyclicBarrier 相比 CountDownLatch 来说,要简单很多,源码实现是基于 ReentrantLock 和 Condition 的组合使用

CyclicBarrier源码分析


CyclicBarrier(parties)

进入CyclicBarrier默认构造器:

在这里插入图片描述

可以发现,最终其实还是调用的CyclicBarrier(int parties,Runnable barrier- Action)构造器:

在这里插入图片描述

注意了,构造CyclicBarrier对象时,初始化了多少个parties,则必须对应有parties个线程调用await()方法,否则线程不会往后执行。

CyclicBarrier#await()

在这里插入图片描述

调用了dowait(timed,nanos)方法,第一个参数false表示未设置超时时间,后面表示纳秒数,因为await还有另一个对应的方法带上超时时间:await(long,timeunit),这个方法中调用dowait(timed,nanos)方法时第一个参数就会是true,然后带上超时时间,表示到了设定时间之后线程就不会被阻塞,会继续往后执行。

CyclicBarrier#dowait()

/**

  • Main barrier code, covering the various policies.

  • 主要屏障代码,覆盖了各种策略

*/

private int dowait(boolean timed, long nanos)

throws InterruptedException, BrokenBarrierException,

TimeoutException {

final ReentrantLock lock = this.lock;//定义一个重入锁:private final ReentrantLock lock = new ReentrantLock();

lock.lock();

try {

//同一个屏障初始进来时属于同一代或者说一个周期,构建一个"代"(Generation)对象,同一个Generation表示同一代

final Generation g = generation;//Generation中设置了broke=false,表示屏障没有损坏

if (g.broken)//如果broken=true表示当前屏障被损坏了,抛出异常

throw new BrokenBarrierException();

if (Thread.interrupted()) {//如果线程被中断过

breakBarrier();//设置屏障为损坏状态并唤醒所有持有锁的线程

throw new InterruptedException();//抛出中断异常

}

int index = --count;//未调用await()方法的线程计数-1

if (index == 0) {//如果屏障数为0,(表示所有线程都到达await()方法)

boolean ranAction = false;

try {

final Runnable command = barrierCommand;

if (command != null)

command.run();//表示到达屏障之后,如果我们有设置barrierCommand,则优先执行

ranAction = true;

//执行到这里的时候,说明所有线程都到了await()方法,且设置的barrierCommand也已经执行完了

//接下来要做的事情就是换代(所以CyclicBarrier是通过换代的方式实现重新计数的)

//换代之后相当于进入一个新的周期,所有线程在后续中又可以通过await()阻塞一次

nextGeneration();

return 0;

} finally {

if (!ranAction)//如果ranAction = false说明当前屏障还有流程没执行完,所以需要屏障设置会损坏状态

breakBarrier();

}

}

// loop until tripped, broken, interrupted, or timed out

//死循环等到count=0,调用breakBarrier方法(表示屏障有问题的场景),中断或者超时

for (;😉 {

try {

if (!timed)

//private final Condition trip = lock.newCondition();

trip.await();//即Condition队列的await()阻塞,相当于把线程加入到Condition队列中阻塞

else if (nanos > 0L)//超时时间大于0

nanos = trip.awaitNanos(nanos);//阻塞指定时间

} catch (InterruptedException ie) {

//如果当前屏障没有换代,也没有损坏,那么就设置为损坏状态之后再抛出中断异常

if (g == generation && ! g.broken) {

breakBarrier();

throw ie;

} else {

// We’re about to finish waiting even if we had not

// been interrupted, so this interrupt is deemed to

// “belong” to subsequent execution.

Thread.currentThread().interrupt();

}

}

if (g.broken)//如果屏障已经被损坏了

throw new BrokenBarrierException();

if (g != generation)//如果发现已经换代了,就不继续循环了,直接返回就好了

return index;//返回当前还有多少个线程没有执行await()方法

if (timed && nanos <= 0L) {//表示超时时间到了

breakBarrier();

throw new TimeoutException();

}

}

} finally {

lock.unlock();

}

}

这里的方法看起来很长,但其实除了一系列的判断,并没有多代码,结合注释,如果前面理解了ReentrantLock和Condition队列的话,应该非常好看懂,里面调用的其他一些子方法这里也不做单独介绍。

使用CyclicBarrier注意事项


1、对于指定计数值 parties,若由于某种原因,没有足够的线程调用 CyclicBarrier 的await()方法,则所有调用 await 的线程都会被阻塞;

2、若有多余线程执行了await()方法,那么最后一个到达屏障的线程会被阻塞

3、通过 reset 重置计数,会使得进入 await 的线程出现BrokenBarrierException;我们可以通过捕获异常重新处理业务逻辑

4、如果采用是 CyclicBarrier(int parties, Runnable barrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程。

CountDownLatch和CyclicBarrier区别

===========================================================================================

  • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数 器,并让线程重新执行一次。

Semaphore

======================================================================

Semaphore也就是我们常说的信号灯,Semaphore可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流

的作用。叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。

Semaphore使用示例


package com.zwx.concurrent.jucUtil;

import java.util.concurrent.Semaphore;

import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {

public static void main(String[] args) {

Semaphore semaphore=new Semaphore(2);

for(int i=1;i<=5;i++){

new Car(i,semaphore).start();

}

}

}

class Car extends Thread{

private int num;

private Semaphore semaphore;

public Car(int num, Semaphore semaphore) {

this.num = num;

this.semaphore = semaphore;

}

@Override

public void run() {

try {

semaphore.acquire();//获取一个许可

System.out.println(“第”+num+“辆车进来了”);

TimeUnit.SECONDS.sleep(2);

System.out.println(“第”+num+“辆车出去了”);

semaphore.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料


.num = num;

this.semaphore = semaphore;

}

@Override

public void run() {

try {

semaphore.acquire();//获取一个许可

System.out.println(“第”+num+“辆车进来了”);

TimeUnit.SECONDS.sleep(2);

System.out.println(“第”+num+“辆车出去了”);

semaphore.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料

[外链图片转存中…(img-vxDz2AyA-1714526731716)]
[外链图片转存中…(img-2A6zVbXP-1714526731716)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值