并发编程之条件队列CyclicBarrier(栅栏)

【JUC】循环屏障CyclicBarrier详解 jdk中提供了许多的并发工具类,大家可能比较熟悉的有,主要用来阻塞一个线程运行,直到其他线程运行完毕。而jdk还有一个功能类似并发工具类,你知道它的作用吗?和有什么区别呢?对于CountDownLatch不了解的可以参考# CountDownLatch源码硬核解析,循环屏障,用来进行线程协作,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,会触发自己运行,运行完后,屏障又会开门,所有被屏障拦截的线程又可以继续运行。所以是可以重用的。 阅读详情

CyclicBarrier栅栏

CyclicBarrier栅栏是AQS中除共享锁、独占锁之外的一种条件锁的一种实现,CyclicBarrier和semaphor以及countDownlatch在实现上有一定的区别,CyclicBarrier是通过条件锁以及独占锁ReentrantLocak实现的;
CyclicBarrier不同于semaphor和countDownlatch,信号量semaphor定义的资源数,在资源用完的时候,进入的线程被阻塞,然后已经获取资源的线程执行完业务逻辑会释放,然后会唤醒处于阻塞的线程,这是共享锁的实现,而CountDownLatch主要是阻塞主线程,在计数器不为0的时候主线程一种阻塞,这种可以用于闭锁的场景,也是共享锁的实现;而CyclcBarrier和前面两个不同在于它使用独占锁ReentrantLocak和条件锁Condition来实现的,而且栅栏是要具备一定的条件才能启动线程,生活中有很多例子,比如打麻将,必须要4个人才能开一桌,斗地主也是必须要3人才能开一座等等例子,也就是说栅栏必须要符合开始的条件才能开启,比如这种业务需要4个线程一起协作完成,那么不管哪个线程先到,都要先被阻塞,等符合4个线程的条件时,会唤醒处于等待的4个线程,然后开始执行。如果说一直不满足条件的话会一直阻塞到条件队列中,比如我又5个线程要做事,那么我的栅栏设置为3个线程做事,那么会出现什么情况呢,前3个线程开始了做事,有一个线程一直处于等待,因为人不齐,不能开,所以就一直处于等待的状态,比如我们看下图:
在这里插入图片描述
可能下图更形象一点,我从网上找的 :
在这里插入图片描述
CyclicBarrier在实现上有一定的区别,我们通过前面的CountDownlatch和Semaphor知道,队列的唤醒是在AQS的双向链表队列中进行唤醒的,而且队列的结构是head结点的thread=null,waitStatus=-1唤醒下一个结点,也就是说只有waitSttus=-1才有权限唤醒下一个结点;而Condition是单向列表的结构,也就是说被阻塞的条件线程是放入到了单向链表的结构中,如果要唤醒线程,那么还必须做一个事情就是Condition队列转换为AQS的同步队列Node;
所以实现上有一定的区别已经难度,当然了,如果把前面的ReentrantLocak、Semaphor、CountDownlatch都看懂了,那么CyclicBarrier也就很简单了;AQS条件队列转换成同步队列大概操作如下:
在这里插入图片描述

Condition队列

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

// ========== 阻塞 ==========   
// 造成当前线程在接到信号或被中断之前一直处于等待状态。
void await() throws InterruptedException; 
// 造成当前线程在接到信号之前一直处于等待状态。
void awaitUninterruptibly(); 
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,
// 如果在`nanosTimeout` 之前唤醒,那么返回值 `= nanosTimeout - 消耗时间` ,如果返回值 `<= 0` ,
//则可以认定它已经超时了。
long awaitNanos(long nanosTimeout) throws InterruptedException; 
// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
boolean await(long time, TimeUnit unit) throws InterruptedException; 
// 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回 // true ,否则表示到了指定时间,返回返回 false 。
boolean awaitUntil(Date deadline) throws InterruptedException; 
// ========== 唤醒 ==========
// 唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。 pthread_cond_signal
void signal(); 
// 唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。
void signalAll(); 

AQS内部定义了ConditionObject

public class ConditionObject implements Condition, java.io.Serializable {    
    /** First node of condition queue. */    
    private transient Node firstWaiter; // 头节点    
    /** Last node of condition queue. */    
    private transient Node lastWaiter; // 尾节点        
    public ConditionObject() {    }    // ... 省略内部代码
}

CyclicBarrier实现原理

下面我们通过一个例子是分析下CyclicBarrier的实现原理

public class CyclicBarrierTest {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        for(int i = 1 ;i <=5 ;i++){
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"等待人齐");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread().getName()+"人齐开始");
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName()+"任务完成");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }finally {
                }


            },"Thread"+i).start();
        }
    }
}

在这里插入图片描述
我们的定义的栅栏是3个线程开始执行,而我们启动了5个线程,所以第2个和第5个线程一直阻塞,因为达不到CyclicBarrier的执行条件,所以一直处于阻塞的状态.

构造方法

//传入资源数量,也就是说需要等待parties线程获取了锁才能执行,才能唤醒
//parties个线程开始工作
public CyclicBarrier(int parties) {
    this(parties, null);
}
//调用了这个构造方法,这个构造方法就是把资源数赋值给全局的的parties和count
//count这个参数也是代表了资源数的,后续再await中会用到
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    //这个是一个Runnable的线程业务处理类,如果配置了,那么在唤醒的时候还会执行里面
    //的run方法
    this.barrierCommand = barrierAction;
}

下面重点分析下await方法以及CyclicBarrier的整个执行流程

//await方法是CyclicBarrier的精髓所在,它的实现都是依靠await来实现的
//简单来说就是所有的线程在这里获取资源后被阻塞,然后等待最后一个线程获取了资源然后
//从队列中通知大家人齐了,可以开始干活了,这里面还涉及到一个就是从条件队列转换成
//同步队列的过程,因为线程的唤醒都是通过同步队列来唤醒的,所以CyclicBarrier在
//入队的时候都是进入的条件队列,条件队列是单向链表结构,所以如果需要唤醒,那么
//就必须转换成同步队列,然后出队,CyclicBarrier和前面的共享锁有一定的区别
//同步队列是获取了锁过后就可以执行自己的业务逻辑,而CyclicBarrier需要等待线程
//筹齐了才能执行各自的业务逻辑,所以这点要区分开来,先要理解它的思想,再来分析它
//的实现
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
//CyclicBarrier是通过ReentrantLocak以及Condition条件锁来实现的
//这里为什么要使用ReentrantLocak呢?因为我们知道前面的共享锁也好,独占锁
//也好,都在使用cas操作来保证线程数据安全,而这里用ReentrantLocak主要是保证
//线程获取资源锁的有序性,也就是说如果3个资源,那么每个资源都是有序的获取
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //定义了一个栅栏是否破烂的对象,如果乍莱已经破烂
        //抛出异常
        final Generation g = generation;

        if (g.broken)
            throw new BrokenBarrierException();

//如果先被中断,那么调用breakBarrier()方法重置资源数,然后通知所有处于等待的线程
        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }

//这里就是真正需要我们关心的部分,首先资源数-1,count=通过构造传入的资源数
        int index = --count;
        if (index == 0) {  // tripped
        //如果index=0 ,那么证明人齐了,也就是资源数都被线程获取了,人齐了,可以
        //开始工作了
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                //如果在构造中配置了Runnable,那么这里会调用它的run方法
                //算是一个扩展吧,就是在线程被唤醒之前可以执行一段自己定义的业务逻辑
                if (command != null)
                    command.run();
                ranAction = true;
                //下面这个是唤醒的精髓,后面讲
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                //下面的if判断是判断是否配置了时间,如果没有,那么直接阻塞
                //如果配置了,那么在指定的时间内返回纳秒数
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    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;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}

Condition.await()

//这个是条件锁的等待阻塞方法,包含了ReentrantLocak的解锁,资源释放以及入队操作
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
        //获取资源的线程这个时候还不满足执行条件,需要等待,所以先入队
        //这个队列是单向链表结构,默认是尾插法,就是入到尾部
    Node node = addConditionWaiter();
    //入队成功过后,因为前面的方法还阻塞了ReentrantLocak,所以这里要释放这把锁
    //让后面获取到资源的线程可以依次入队
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //isOnSyncQueue这个是判断是否是同步队列,这里肯定返回false,非false也就是true
    //因为我们是条件队列,所以这里肯定能进去while
    while (!isOnSyncQueue(node)) {
        //这里好直接啊,只要你不是同步队列,好的,那么直接给你park了阻塞到这里
        //我们看源码,虽然是静态代码,那么还是要一定的逻辑,到这里是阻塞,
        //那么如果别的地方调用这个线程的unpark,那么这个线程还是会从这里开始唤醒执行
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //竞争同步状态
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
          // 清理下条件队列中的不是在等待条件的节点
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
//这个就是上面所说的将获取到资源的线程添加到条件队列中
private Node addConditionWaiter() {
    //条件队列只有first和last,是单向链表结构,所以一般入队都是通过尾部入队
    //所以 Node  t = lastWaiter在第一次肯定是空的,这个时候就需要构建一个
    //新的队列,构建的新的队列的first=last的,当第二个线程入队的时候那么first
    //和last就都不是为空的
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //判断入队的结点是不是waitStatus 是不是-2(Node.CONDITION)
    //我们通过前面的分析知道同步队列的head用于都是-1,-1是有权限唤醒下一个结点
    //而这里是-2,是条件队列的waitStatus的特有值,所以这判断下如果不是-2
    //执行unlinkCancelledWaiters就是解散非-2的条件队列
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //这里构建一个结点,结点的waitStatus=-2,thread=null
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
    //如果t=null则证明是第一次构建队列,那么first=next的
        firstWaiter = node;
    else
    //否则就入队,默认是从尾部进队,所以t=last,那么t.next就是从新定义了node为尾部
    //结点
        t.nextWaiter = node;
        //最后复制尾部结点为node
    lastWaiter = node;
    return node;
}
//这里干的事情就是释放ReentrantLocak锁,让其他线程可以入队(Condition条件队列)
//入队的目的就好比是你如果在等人吃饭,那么你的进门的时候先拿到钥匙,你进去了要等待
//之后的人,那么你一直持有钥匙,那么别人也进不来啊,所以这里就是把这个钥匙换回去
//也就是把自己持有的锁释放了,让别人进来,这里涉及到了ReentrantLocak的解锁
//加锁在最开始的加的
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        //savedState 肯定是1,关于state可以去看下ReentrantLocak独占锁的实现原理
        //这里就不赘述了
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

如果定义了资源数为3,当3个线程都进入条件队列过后,那么我们的代码分析回到CyclicBarrier中的await方法,如下:

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;

        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
//这里是CyclicBarrier中的await方法的中间部分
//如果资源数为3,已经有2个线程入队了,那么当第三个线程过后的时候,发现是已经是0了
//那么这里就要开始唤醒的等待队列中的线程,告诉他们要开始工作了,
//而我们前面分析了代码知道在 trip.await();是要释放ReentrantLocak锁的
//所以这里如果是第三个线程,那么这第三个线程是不可能进入 trip.await();方法的
//因为代码到这里已经加了ReentrantLocak锁的,所以在finally中要unlock,否则第三个线程一直
//占有锁肯定是不行的
        int index = --count;
        if (index == 0) {  // tripped
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    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;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
//真正唤醒线程的方法,我们先看这个方法的几行代码
//当执行了  trip.signalAll();过后,又把资源数重置为之前通过构造进入的资源数
//所以到这里也就可以理解了CyclicBarrier是一个可以重复利用的栅栏
//满足条件的线程进入栅栏,栅栏开启,线程走了过后又重置为原有的状态,下一个队列
//满足条件又可以进入栅栏
private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

signalAll

//唤醒所有的线程(其实针对CyclicBarrier来说,这里只是将条件队列转换为
//同步队列,并没有唤醒线程)
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
        //单向链表只有first和last,所以这里先获取first
        //如果first==null,那么就根本没有节点数据
        //这里获取到第一个节点开始入队到同步队列
    Node first = firstWaiter;
    if (first != null)
      //循环入队,从条件队列转换为同步队列
        doSignalAll(first);
}
private void doSignalAll(Node first) {
    //首先代码第一行就打散了条件队列的结构
    //执行了下面的代码过后,条件队列的首尾就空了,队列的数据
    //就分布在内存空间的不同块cell上了
    lastWaiter = firstWaiter = null;
    do {
        //单向链表出队操作,这边代码的逻辑是这样的:首先获取first
        //的下一个元素赋值给next,然后将下一个元素设置为空
        //然后将first本身入同步队列,然后循环下一个元素,直到元素为空为止
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        //将出队的元素加入到同步队列中
        transferForSignal(first);
        first = next;
    } while (first != null);
}
//条件队列转同步队列,针对单个节点
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
     //条件队列的waitStatus=-2的,而且这个入队操作是在lock的状态下
     //进行的,所以这里的cas修改为0肯定能成功,所以if是的条件是不会进去的
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
     //这个enq方法很熟悉,前面一直在用,就是入队同步队列
     //在这个方法里面,如果第一次进入enq队列,会先创建一个head,new Node()
     //作为同步队列的head节点,然后条件队列中的元素都入到head的后面
     //也就是说第一个从条件队列入同步队列的数据默认就是tail,
     //head.next = node=tail
     //简单来说就是尾插法,enq方法前面的独占锁ReentrantLock已经讲了
     //这里不做解析;Node p就是同步队列的head
    Node p = enq(node);
    int ws = p.waitStatus;
    //看下面的if条件,有2个,ws目前是0,所以ws >0 条件为false,
    //再看第二个条件,这个时候修改ws为-1,也就是可以被唤醒
    //这里的CAS就是将head的waitStatus设置为-1,因为同步队列中永远
    //是-1唤醒下一个队列,而当前运行的线程就是head,也就是
    //head目前占有锁
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

代码执行到这里是不是感觉缺失了什么呢?是不是还缺少了唤醒处于队列中的线程,这个时候前面的线程还阻塞在
在这里插入图片描述
而这个时候线程是被同步队列的head也就是第三个线程占有到在,也就是
在这里插入图片描述
刚刚是第三个线程发现人气了,所以第三个线程做的工作是将前面两个线程从条件队列转换为同步队列,但是第三个线程没有唤醒它们,所以唤醒工作还没有做,在哪里做呢?很显然是在unlock里面做了,也就是

在这里插入图片描述
所以我们还得看下独占锁ReentrantLock的解锁业务逻辑如下:

public void unlock() {
    sync.release(1);
}
//tryRelease这个方法很简单,在ReentrantLock已经讲过,就是释放锁
//将当前的线程设置为null,然后state-1也就是state=0
//所以我们重点看下if条件里面的代码
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        //下面这些代码Doug Lea写的很精髓,
        //这个是独占锁的解锁逻辑,也就是说CyclicBarrier利用了ReentrantLock
        //的解锁逻辑来依次唤醒进入同步队列的线程
        //但是不知道在读这篇笔记的你有没有发现,CyclicBarrier在
        //刚开始构建条件队列时候,当条件没有满足的时候也是调用的release来
        //释放锁的,但是当那个时候同步队列还没有构建的,所以这个时候
        //head肯定是空的,而条件队列构建完毕,当第三个线程过来的时候
        //发现条件满足了,那么就会唤醒线程,在唤醒线程的过程中分两步:
        //1.将条件队列转换成同步队列
        //2.调用ReentrantLock的解锁逻辑来依次唤醒处于等待的线程
        //所以这就是Doug lea写的代码
        Node h = head;
        if (h != null && h.waitStatus != 0)
        //这里是ReentrantLock的唤醒逻辑,在ReentrantLock已经解析了
            unparkSuccessor(h);
        return true;
    }
    return false;
}

上面的依次解锁过程解锁了过后,线程1和线程2还处于await的流程中
在这里插入图片描述

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //解锁过后要跳出循环,所以checkInterruptWhileWaiting就返回了1
        //怎么返回的1呢?我们看下代码分析
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //跳出循环过后,这里acquireQueued获取锁,获取锁成功过后又会跳到
    //CyclicBarrier中的doawit方法,然后再调用unlock,唤醒下一个线程
    //流程就是一样的了
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
private int checkInterruptWhileWaiting(Node node) {
    //这里返回的是1
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

//核心代码如下,这个时候node的waitstatus=-1,所以,这里返回的是false
final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin.
     */
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

上面就是CyclicBarrier的执行逻辑,加锁解锁唤醒的逻辑不算复杂,但是流程执行比较绕,绕的晕晕的,但是思想是非常好的,就是利用了ReentrantLock和Condition来实现的,首先在不满足的条件下 ,全部入条件队列,当最后一个满足条件的线程过来以后,然后将条件队列转换成同步队列,然后使用ReentrantLock的逻辑依次唤醒刚刚转换成同步队列的线程,然后被唤醒的线程重新获取锁,获取锁的目的就是唤醒下一个线程,就这样的一个反复过程,当同步队列都被唤醒过后,所有线程开始执行业务逻辑,也就是这一个满足的条件线程都去执行去了,然后条件又初始化为原始状态,接收下一组满足条件的线程。这个和独自锁区别在于,独自锁是在获取了锁以后执行自己的业务逻辑,然后释放锁,其他线程再去竞争锁,而CyclicBarrier在唤醒的时候,是依次全部唤醒,然后做各自的事情,就是全部唤醒过后,每个线程执行自己的业务逻辑
在这里插入图片描述
也就是说await过后,下面的人齐开始的线程是并行开始的,这就是CyclicBarrier的思想和实现原理,这边做一个记录,以便后面翻出来看看

CyclicBarrier执行流程(图解)

我们就以上面的一个例子通过简单的图形来还原下CyclicBarrier的执行流程,我们启动5个线程,而循环屏障CyclicBarrier设置的资源条件是3个,那么肯定有2个线程一直在等待,因为人不齐,CyclicBarrier条件要求的是3个线程,我再贴下代码,如下:

执行流程图:
在这里插入图片描述
上面是以5个线程为主,分析CyclicBarrier的执行流程,多的就不说了,在源码层面说的很清楚了,结合上图就可以很好的理解CyclicBarrier的思想和原理,好了,本编笔记到此就结束了。

结语:并发编程的笔记大概就写这么多吧,其实还想写一遍线程分治框架forkjoin,但是forkjoin的底层原理太复杂,但是思想很好,现在用的也不是很多,后面看是否有机会,如果有机会再去写。

开关电源三大基本拓扑:反激、正激与桥式选型本质 开关电源拓扑是电力电子系统设计的核心基础概念,其本质决定能量传递路径、器件应力分布与系统物理边界。反激拓扑以耦合电感形式实现隔离与升降压,适用于小功率AC-DC场景;正激拓扑依赖实时能量传递与强制复位机制,支撑中功率高效率需求;桥式拓扑(含推挽/半桥/全桥)通过电压应力分摊与双向磁化,成为大功率系统的刚性选择。理解三者在磁芯工作模式、电流连续性、直流偏磁抑制等方面的原理差异,直接关系到效率、温升、EMI及可靠性等关键技术指标。本文聚焦反激、正激、桥式三大工程级基本拓扑的物理本质与落地约束,揭示从需求参数到电 阅读详情

相关推荐

医疗影像技术实战:如何用Python快速生成DRR图像(附完整代码)

本文详细介绍了如何使用Python实现数字重建放射影像的快速生成。通过实战代码,从基础光线投射算法到性能优化,逐步讲解DRR图像生成的核心原理与工程实践,帮助开发者掌握医疗影像处理的关键技术,赋能放射治疗规划与图像引导手术等临床工具开发。

k8l9m0n的博客 72

线程池环境下,慎用循环栅栏CyclicBarrie

线程池环境下,慎用循环栅栏CyclicBarrie

ShangHai0123的博客 2626

ROST_CM6软件之词频分析、社会网络和语义网络分析、情感分析

简介 ROST CM 6是武汉大学沈阳教授研发编码的国内目前唯一的以辅助人文社会科学研究的大型免费社会计算平台。该软件可以实现微博分析、聊天分析、全网分析、网站分析、浏览分析、分词、词频统计、英文词频统计、流量分析、聚类分析等一系列文本分析。 社会网络和语义网络分析(S) 数据 注意,保存时编码要为ANSI 所以这里需要转换一下 准备自定义表与停词表 例如 同时,点击快速分析后,将会弹出网络图 对网络图进行设置 对布局进行设置 对节点、边进行设置 最终效果图 修改 情感分析

积一时之步,臻千里之遥程 9万+

【线程知识点】-- 栅栏:CyclicBarrier

1请输入标题首先看下JDK中的介绍:/** * A synchronization aid that allows a set of threads to all wait for * each other to reach a common barrier point. CyclicBarriers are * useful in programs involving a fixed s...

轰鸣 1325

Vue集成飞书前端JSSDK

文章目录前言<1>飞书前端JSSDK<2>使用步骤1.将JSSDK引入到VUE2.读入数据总结 前言 公司需要使用飞书软件,所以转战飞书开发,也是对新知识的探索与研究,飞书SDK也一直都在更新,路虽远,行必达。 <1>飞书前端JSSDK 1、官网地址:https://open.feishu.cn/document/uYjL24iN/uITO4IjLykDOy4iM5gjM 2、当前使用的飞书前端JSSDk的版本:1.5.4 <!-- 在html文档中 --&gt

A 1万+

一文带你完成ARMv8架构中的cache知识点

【摘要】 在最初开发ARM架构时,处理器的时钟速度和内存的访问速度大致相同。今天的处理器内核要复杂得多,其时钟速度可以快上几个数量级。但是,外部总线和内存设备的频率并没有扩大到同样的程度。有可能实现小块的片上SRAM,它可以以与内核相同的速度运行,但是与标准的DRAM块相比,这种RAM非常昂贵,因为后者的容量可以达到数千倍。在许多基于ARM处理器的系统中,访问外部存储器需要几十甚至几百个内核周期。 cache的技术背景 在最初开发ARM架构时,处理器的时钟速度和内存的访问速度大致相同。今天的处.

ABCCloud的博客 1854

一篇文章彻底搞懂AQS工具类之CyclicBarrier(深度剖析)

围绕AQS工具类之CyclicBarrier(回环栅栏)、底层Conditon实现(等待条件队列)、如何使用、使用场景、源码分析、模拟多个线程获取资源阻塞流程图分析

大尾巴狼学编程的博客 686

从官方例程到实战:AXI Memory Mapped To PCIe 核在FPGA-RC模式下的配置与调试全解析

本文详细解析了AXI Memory Mapped To PCIe核在FPGA-RC模式下的配置与调试方法。从IP核基础结构到实战参数设置,再到例程代码剖析和调试技巧,全面覆盖了AXI与PCIe协议转换的关键技术点,帮助开发者高效实现FPGA与PCIe设备的高速数据交互。

weixin_42550597的博客 270

c await和java_Java并发包源码学习系列:详解Condition条件队列、signal和await

目录系列传送门:Condition接口Contition是一种广义上的条件队列,它利用await()和signal()为线程提供了一种更为灵活的等待/通知模式。图源:《Java并发编程的艺术》Condition必须要配合Lock一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此await和signal的调用必须在lock和unlock之间...

weixin_39885683的博客 255

具身智能(40):主流 VLA 模型π₀

摘要: π₀是首款专为机器人设计的VLA(视觉-语言-动作)端到端基础模型,通过双阶段训练实现“感知-决策-执行”闭环。其创新点包括:1)FlowMatching技术实现连续平滑动作控制(精度0.3°);2)结合VLM预训练与多机器人实操数据,零样本任务完成率达76.4%,远超同类模型;3)支持模糊指令理解与动态场景适应(如遮挡识别)。开源版本涵盖不同规模模型,适用于工业装配、服务机器人等场景,在延迟(213ms)和稳定性(92%自主恢复率)上显著领先OpenVLA等竞品。

码农17年的博客 512

一篇文章带你搞定数学建模中的线性回归模型(05年长江水质问题示例讲解含代码)

文章目录一、问题分析二、原理讲解三、MATLAB 实现线性回归模型 一、问题分析 根据你的预测分析,如果未来10年内每年都要求长江干流的Ⅳ类和Ⅴ类水的比例控制在20%以内,且没有劣Ⅴ类水,那么每年需要处理多少污水 为了达到题目中所要求的水质,可建立废水排放量与各类水百分比之间的二元线性回归模型,对未来十年的废水排放量进行预测,并确定其与各类水所占百分比之间的函数关系式。通过此关系式,可以计算在这种情况下长江所能承受的最大污水排放量,将这个排放量与预测的排放量进行比较,多出来的部分就是需要每年处理掉的。 二、

南淮北安的博客 2167

CyclicBarrier分析

文章目录(一)概念简介(二)使用场景(三)特点(四)CyclicBarrier源码分析(1)核心成员变量(2)构造函数(3)await方法(核心)(4)辅助方法 (一)概念简介 CyclicBarrier是一个可循环使用的屏障锁,依赖于ReentrantLock和Condition来实现阻塞和唤醒,主要是分为普通阻塞和超时阻塞,利用构造方法初始化指定parties(屏障数)和count(剩余屏障数),只需要使用await即可让count值持续递减。基于Condition条件,屏障锁对于屏障数分为多组,每组中

小萌新的博客 331

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

并发编程-同步锁学习笔记

文章目录synchronized(隐式锁)底层原理锁升级对象头偏向锁轻量级锁自旋锁重量级锁锁消除逃逸分析Lock(显示锁) synchronized(隐式锁) 同步实例方法:锁加载在类上,new出来的不同的对象也可以保持同步。 同步类方法:锁加载非static方法,锁的是this对象,new出来的不同的对象不会同步。 同步代码块:只锁当前代码块。 底层原理 synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监

cainiao_xiaoniao的博客 465

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

并发编程-08之CyclicBarrier

CyclicBarrier字面意思回环栅栏(循环屏障),通过它可以实现让一组线程等待至某个状态(屏障点)之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用,本章会从源码分析它为什么可以让一组线程进行协作,以及重置的原理

落雪 1137

并发编程之线程协作

在谈论到多线程的时候,大部分都是线程之间资源竞争,但是也有时候,线程之间也是需要相互协作的。此文我们就来探讨一下线程之间是如何协作的? wait/notify(等待通知) 首先要知道 wait和notify/notifyAll都是Object的方法,而不是Thread的方法,这使得每个对象都能调用这几个方法。上回谈到synchronized加锁的时候,每个对象都有一把锁,是通过对象头来实现的,而且对象还有一个等待队列,当线程获取不到对象锁的时候,会进入等待队列。除此之外,对象还有另外一个等待队列,这是个.

pipi蛋 243

CyclicBarrier栅栏

CyclicBarrier有什么作用? 在现实生活中,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始。 栅栏就可以很好的模拟这类场景,利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。下图演示了这一过程。   CyclicBarrier...

怪咖小青年的博客 337

【Java并发编程】循环屏障CyclicBarrier详解

CyclicBarrier 是一个线程同步工具类,用于协调一组线程,使其在达到某个共同点(屏障)后再一起继续执行。与 CountDownLatch 不同,CyclicBarrier 可以重复使用,每次达到屏障后,都会开启新一代线程,并重置屏障状态。CyclicBarrier 主要通过 ReentrantLock 和 Condition 实现,并提供了 await() 方法用于线程阻塞等待,以及 breakBarrier()、nextGeneration() 和 reset() 等工具方法用于控制屏障状态。

叉烧钵钵鸡的博客 1456

深入理解CyclicBarrier原理

1、CyclicBarrier的简单概述 现实生活中我们经常会遇到这样的情景,在进行某个活动前需要等待人全部都齐了才开始。例如吃饭时要等全家人都上座了才动筷子,旅游时要等全部人都到齐了才出发,比赛时要等运动员都上场后才开始。 在JUC包中为我们提供了一个同步工具类能够很好的模拟这类场景,它就是CyclicBarrier类。利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到...

qq_39241239的博客 4万+

【并发编程】回环栅栏CyclicBarrier从入门到源码精通

一篇文章从源码带你了解多线程计算数据,最后合并计算结果或者“人满发车”的实现思路。

qq_35436158的博客 646

聊聊高并发(三十)解析java.util.concurrent各个组件(十二) 理解CyclicBarrier栅栏

这篇讲讲CyclicBarrier栅栏,从它的名字可以看出,它是可循环使用的。它的功能和CountDownLatch类似,也是让一组线程等待,然后一起开始往下执行。但是两者还是有几个区别 1. 等待的对象不同。CountDownLatch的一组线程等待的是一个事件,或者说是一个计数器归0的事件。而CyclicBarrier等待的对象是线程,只有线程都到齐了才往下执行 2. 使用方式不同,这个也

ITer_ZC的专栏 4485

Java并发编程CyclicBarrier

一、CyclicBarrier循环栅栏 CyclicBarrier是java.util.concurrent包下面的一个工具类,字面意思是可循环使用(Cyclic)的屏障(Barrier),通过它可以实现让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,所有被屏障拦截的线程才会继续执行。 CyclicBarrier循环栅栏和CountDownLatch很类似,都能阻塞一组线程 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须

muli525的博客 503

Java并发编程CyclicBarrier CyclicBarrier是java.util.concurrent包下面的一个工具类

Java并发编程CyclicBarrier CyclicBarrier是java.util.concurrent包下面的一个工具类

m0_58745896的博客 154

Java并发编程CyclicBarrier之小白教程

Java并发编程CyclicBarrier之小白教程

m0_74164049的博客 268

十:深入理解 CyclicBarrier—— 栅栏锁

让一组线程到达某个屏障,被阻塞,一直到组内的最后一个线程到达,然后屏障开放,接着,所有的线程继续运行是通过独占锁实现的,底层包含了 “ReentrantLock 对象 lock” 和 “Condition 对象 trip”,通过条件队列 trip 来对线程进行阻塞的,并且其内部维护了两个 int 型的变量 parties 和 count。

sco5282的博客 810
上一篇: 并发编程之AQS(抽象队列同步器)共享锁
下一篇: Spring源码中涉及到的基本概念详解
Life  journey
博客等级 码龄17年 1万+粉丝 80原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值