一、 故事开始

时光匆匆,岁月不饶人,随着年龄的增长,我们肩上的担子越来越大了,除了本职工作,我们还想着通过其他途径再赚点儿外块,补贴家用(自己小金库)。

话说这天我的好友–小强,周末背【bèi】着媳妇儿去搬砖赚零花钱。

工地上有包工头的小侄子A、包工头的小姨子B、包工头的小叔子C和小强。

小强一看惊呆了,TMD一共四个人,就有三个关系户,这年头搬个砖也得靠关系吗?

没办发,为了赚点儿钱,小强还是硬着头皮准备干了。

包工头先进行了一项测试,测试小强的力气,看小强一次能搬多少块砖,经过测试小强只能一次搬三块,从砖窑搬到车上。

包工头开始分工了: A、B、C负责把砖放到小强手上,小强搬到车上,然后循环这个流程

好友程序员,跑去搬砖了_回调函数

​ 时间过得很快,不知不觉一天过去了,小强满是疲惫的去找包工头结账,包工头给了他150元RMB,小强一看天色已晚,没有公交车回家了,只能打车,然后100块打了个车回家,回去为了犒劳自己除了顿好吃的,花了100,晚上回家算账发现一天不仅没有赚钱,还多支出了50。

​ 越想越生气的小强拿出电脑,准备把今天的遭遇写一篇博客,来让广大网友们给自己点儿安慰。

​ 于是他找到了他的朋友小明,来代笔帮他写一篇博客,身为技术人员的小明,当然不喜欢白话文写喽,直接把他的搬砖经历写成了代码。代码如下:

package com.example.democyclicbarrier;
import java.util.concurrent.CyclicBarrier;
/**
 * @author 发现更多精彩  关注公众号:木子的昼夜编程
 * 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
 * @create 2021-08-15 10:07
 */
public class TestCyc {
    public static void main(String[] args) {
        System.out.println("小强准备赚钱...");
        System.out.println("测试小强力气...");
        // 重点关注代码
        CyclicBarrier barr = new CyclicBarrier(3, ()->{
            try {
                System.out.println("够三块了,小强吭哧吭哧搬到车上去...");
                Thread.sleep(2000);
            } catch (Exception e) {
                System.out.println("小强不干了...");
            }
        });
        System.out.println("测试结果:一次能搬"+barr.getParties()+"块");
        System.out.println("开始搬砖...");
        // 假设一天搬5次
        new Thread(()->{
            for (int i = 0; i < 5 ; i++) {
                try{
                    System.out.println("A放一块砖到小强手里.");
                    barr.await();
                    // 够三块了 才会执行下边代码逻辑
                } catch (Exception e) {
                    System.out.println("A不干了");
                }
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 5 ; i++) {
                try {
                    System.out.println("B放一块砖到小强手里.");
                    barr.await();
                    // 够三块了 才会执行下边代码逻辑
                } catch (Exception e) {
                    System.out.println("B不干了");
                }
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 5 ; i++) {
                try {
                    System.out.println("C放一块砖到小强手里.");
                    barr.await();
                    // 够三块了 才会执行下边代码逻辑
                } catch (Exception e) {
                    System.out.println("C不干了");
                }
            }
        }).start();
    }
}

输出结果:

好友程序员,跑去搬砖了_回调函数_02

小强也是够苦的,三个人搬到他手上,他一个人搬到车上,对于我们做后端开发的何尝不是呢??

产品+前端+测试 对接我们后端开发 。

产品出个需求,前端写完代码要跟我们后端联调,测试催着我们赶快开发早点儿投入测试 我们后端只能跟小强一样吭哧吭哧的做。 做到最后发现手里的钱不足以支撑自己活下去呀。。。。

这个例子里用到了一个知识点,CyclicBarrier 字面意思是“循环栅栏”

使用方式上边已经写了,基本上就那样用,再总结一下他的具体作用,就是设置一个基础值parties(也就是小强一次能搬多少块砖) ,再给一个回调函数(也就是达到parties后小强需要做的事儿) ,然后就是await方法了(也就是A、B、C的工作 await一次就离回调更进一步)
好友程序员,跑去搬砖了_公众号_03

二、知识的解析

我们不妨看一下源码

2.1 创建

// parties 设置的线程等待数 也就是需要几个await就会唤醒回调barrierAction执行
// barrierAction 回调 await的线程数到达parties个数就会执行这个回调
public CyclicBarrier(int parties, Runnable barrierAction) {
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    // 有人奇怪说这个count是干啥的 他就是一个中间量 先设置为parties的值
    // 然后每次await count就会减去1 可以看上边那个图 
    // 等count等于0的时候 就执行回调以及---“改朝换代”
    this.count = parties;
    this.barrierCommand = barrierAction;
}

这个类有5个重要的属性需要关注:

    // 锁 await的时候需要先获取到锁才能执行逻辑代码 
	// 为什么需要锁,总不能十几个人一股脑都往小强手上放砖吧,那不直接出异常情况了嘛
    // 没法区分谁是第一个 谁是最后一个 可能最后一个会执行多次 
    // 反正就是多个线程操作 count 一般情况都需要锁 
	private final ReentrantLock lock = new ReentrantLock();
    // 这是lock锁的一个条件 所有线程都是通过condition的await来阻塞
    // 等await数量足够了也就是count =0 的时候 所有线程会被signalAll
    private final Condition trip = lock.newCondition();
    // 这个不用多说了 就是await最大数量
    private final int parties;
    // 这个是回调 是一个Runnable 
    private final Runnable barrierCommand;
    // 这个是一个代的概念,怎么说呢,就是小强每搬一次砖到车上都会new 一个新的Generation
	// 来承载这次搬砖的信息 主要有一个参数是:broken 标记这一代是否被打断
    private Generation generation = new Generation();
	// 这个就是那个 勤勤恳恳的计数员 从parties到0 一直倒数计数
    private int count;

2.2 await

//  返回index 也就是第几个await的线程,如果index = parties - 1 那就是第一个await的线程  
// 如果等于0 那就是最后一个await的线程 就会执行回调函数了
public int await() throws InterruptedException, BrokenBarrierException {
    try {
        // 
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        // 获取锁 
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 获取当前代
            final Generation g = generation;
            // 如果当前代标记broken为true 那么直接抛异常  (不用重点关注)
            if (g.broken)
                throw new BrokenBarrierException();
			// 如果线程被中断了 设置标记broken为true (不用重点关注
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
			// 下边这是重点
            // 1. count -- 如果结果是0 执行回调、改朝换代
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    // 执行回调: 就是执行barrierCommand这个Runnable
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                     // 改朝换代:就是执行nextGeneration 主要有三个步骤
                    // 第一步:trip.signalAll(); 通知所有await的线程停止await继续往下执行
                    // 第二步:count = parties; 计数器从paeties重新开始倒数计数
                    // 第三部: generation = new Generation(); new 新的generation 
                    nextGeneration();
                    return 0;
                } finally {
                    // 任何异常 都直接终止这一代 也是三步:
                    // 第一步:  generation.broken = true
                    // 第二步: count = parties;
                    // 第三部: trip.signalAll();
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            // 循环等待 被唤醒、这一代被broken、中断 、或者是超时(这个是await的有参方法,可以设置
            // 等待超时时间)
            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();
                // 是否是当前代 这里很关键
                // 一个线程可以使用多个栅栏 这里可能是被其他栅栏唤醒的 不理解这句话 
                // 我理解其实就是breakBarrier()这个方法signalAll的时候唤醒的 非本代的await的线程
                // 如果不相等 说明已经改朝换代 直接返回index
                // 如果没有改朝换代 那就是被别的代唤醒的,自己抓紧回去await等自己代唤醒
                if (g != generation)
                    return index;
                // 超时了 
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            // 划重点 finally 解锁 
            lock.unlock();
        }
    }

几个小方法:

private void breakBarrier() {
    generation.broken = true;
    count = parties;
    trip.signalAll();
}

private void nextGeneration() {
    // signal completion of last generation
    trip.signalAll();
    // set up next generation
    count = parties;
    generation = new Generation();
}

关于await 和 awaitNanos 这是Lock 相关知识 这里不展开叙述

下边是一张流程图:

好友程序员,跑去搬砖了_流程图_04

三、一些事

看博客经常看到拿CyclicBarrier 与 CountDownLatch 比较的 ,我个人觉得没什么比较的,如果如果看过我关于他俩的两篇文章,你就会全懂了。

四、 总结

深耕、广耕自己所在领域知识,争取让自己能靠自己所在领域的知识吃一辈子饭。

更多精彩关注公众号:
好友程序员,跑去搬砖了_公众号_05