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

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的底层原理太复杂,但是思想很好,现在用的也不是很多,后面看是否有机会,如果有机会再去写。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值