JUC源码解析六:CyclicBarrier源码解析

一 前言

前面我们学了一次性的闭锁CountDownLatch,下面我们会接触到一个可以循环的同步辅助类:CyclicBarrier,它允许一组线程互相等待,直到到达某个公共屏障点,在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待。

CyclicBarrier类似于CountDownLatch的地方是它本身也是个计数器,用以计数的变量则是初始化时传入的变量count。不同点则是CyclicBarrier的计数数的是调用了CyclicBarrier.await()而进入等待的线程数,CountDownLatch的计数数则是初始计数值与调用了CountDownLatch.await()而导致的差值。同时前者可以进行循环操作,当一组计数结束后则会开始新一轮的计数,不断循环。CyclicBarrier初始时还可带一个Runnable的参数,此Runnable任务在CyclicBarrier的数目达到后,会在其它线程被唤醒前被执行。

CyclicBarrier的所有API如下:

  • public CyclicBarrier(int parties) 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
  • public CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。
  • public int await() throws InterruptedException, BrokenBarrierException 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
  • public int await(long timeout,TimeUnit unit) throws InterruptedException, BrokenBarrierException,TimeoutException 在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
  • public int getNumberWaiting() 返回当前在屏障处等待的参与者数目。此方法主要用于调试和断言。
  • public int getParties() 返回要求启动此 barrier 的参与者数目。
  • public boolean isBroken() 查询此屏障是否处于损坏状态。
  • public void reset() 将屏障重置为其初始状态。

CyclicBarrier实现主要基于ReentrantLock,不熟悉的可以先看下ReentrantLock的解析。

国际惯例,先看下基本的东西:

一些参数:

	// 用来判断这一代的屏障有没有被broken
	private static class Generation {
        boolean broken = false;
    }

    // 使用独占锁保护屏障入口
    private final ReentrantLock lock = new ReentrantLock();
    // 使用Condition组成等待队列
    private final Condition trip = lock.newCondition();
    // 记录参与等待的线程数
    private final int parties;
    // 当所有线程到达屏障点之后,首先执行的命令
    private final Runnable barrierCommand;
    private Generation generation = new Generation();

    // 仍需要等待加入的线程数,每一代都会从parties降为0
    // 每次到新一代的时候这个值都会被重新设置为parties
    private int count;

Generation用来控制屏障的循环使用,如果generation.broken为true的话,说明这个屏障已经损坏,当某个线程await的时候,直接抛出异常

构造函数:

// 初始化屏障的阀值
    // 当最后一个线程进入屏障时不会执行任务
    public CyclicBarrier(int parties) {
        this(parties, null);
    }
	// 初始化屏障的阀值
    // 当最后一个线程进入屏障时所执行的Runnable任务
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

二 await()与await(long, TimeUnit)

首先是不限时的版本:

	// 将线程挂起直到挂起的线程数量达到paties
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            // 调用了dowait(boolean, long)方法,表明不使用限时
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

只有一行代码,即调用了dowait(long, TimeUnit)方法。

下面看一下限时版本与前者的区别:

	// 希望在规定时间内被执行的await()
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }	

调用的方法与前者是同一个,只不过多了一个时间的参数。下面跳入这个方法:

	// 返回还需要多少个等待线程才能够唤醒所有
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        // 使用独占锁,每条线程lock()和unlock()之间的代码
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            final Generation g = generation;

            // 若generation已被broken,说明其他等待的线程被中断
            if (g.broken)
                throw new BrokenBarrierException();

            // 该线程已被中断
            if (Thread.interrupted()) {
                // 将当前屏障的一代设为broken并且唤醒所有正在等待的线程
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;
            // 到达屏障的阀值,执行之前已设定的任务
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 更新屏障初始的状态位为paties的值并且将所有Condition队列的结点转换到Sync队列
                    // 为唤醒所有处于休眠状态的线程做准备工作
                    // 需要注意的是,唤醒所有阻塞线程不是在这里
                    nextGeneration();
                    return 0;
                } finally {
                    // 发生错误,将当前屏障的一代设为broken并且唤醒所有正在等待的线程
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    // 放入Condition等待队列
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    // 中断后若当前屏障还没有被broken,则唤醒所有结点
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        Thread.currentThread().interrupt();
                    }
                }

                //TODO 这里没怎么看懂啊!!!
                // 当前屏障已被broken
                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                // 将当前屏障的一代设为broken并且唤醒所有正在等待的线程,并抛出超时错误
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            // 解开独占锁
            lock.unlock();
        }
    }

这个是整个CyclicBarrier的核心代码,看懂这个基本就看懂了CyclicBarrier类。

就着上面的注释来讲下具体的步骤:

  1. 使用ReentrantLock独占锁锁定所有代码
  2. 若本次到达屏障的线程存在已被broken的,则抛出BrokenBarrierException异常。(这里不用唤醒所有线程的原因是在broken当前屏障的方法中存在唤醒所有线程的方法)
  3. 若本线程已经被中断,则将当前屏障设置为broken,并且唤醒所有线程
  4. 若调用await()方法的线程数量已达到设置的paties,则将所有在Condition队列中的等待线程转移到Sync队列中,唤醒线程则不在此处实现。(AQS会按照Sync队列顺序依次唤醒线程)
  5. 到此处时说明:此时线程的数量还没有到达设置的阀值。因此会利用循环挂起,直到达到阀值、broken、中断、超时。

此处的方法基本都是调用Condition以及ReentrantLock里面的方法,有兴趣的可以看一下以前的文章。

上面使用到的CyclicBarrier实现的方法则有:

  • breakBarrier():将当前屏障的一代设为broken并且唤醒所有正在等待的线程
  • nextGeneration():更新屏障初始的状态位为paties的值并且将所有Condition队列的结点转换到Sync队列

下面看下:

	// 只能被当前持有锁的线程调用
    // 将当前屏障的一代设为broken并且唤醒所有正在等待的线程
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        // Condition的signalAll()方法,唤醒所有正在等待的线程
        trip.signalAll();
    }
	// 只能被当前持有锁的线程调用
    // 更新屏障初始的状态位并且唤醒所有正在等待的线程
    private void nextGeneration() {
        // 更新屏障初始的状态位为paties的值并且将所有Condition队列的结点转换到Sync队列
        trip.signalAll();
        // 重置count为paties
        count = parties;
        // 设置generation状态为false
        generation = new Generation();
    }

三 其他

	// 查询此屏障是否处于断开状态。
    public boolean isBroken() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return generation.broken;
        } finally {
            lock.unlock();
        }
    }
	// 重新设置屏障,将会唤醒之前的所有的等待线程
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 此处可以思考一下为什么两个差不多功能的方法要连续调用
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }
	// 得到当前正在等待的线程数量
    public int getNumberWaiting() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return parties - count;
        } finally {
            lock.unlock();
        }
    }
	public int getParties() {
        return parties;
    }

四 总结

总的来说,CyclicBarrier是基于ReentrantLockCondition这两个类来实现的,通过ReentrantLock几乎对所有方法的全部字段进行加锁来保证CyclicBarrier的安全性,同时利用Condition将未达到阀值的线程加入等待队列中。 相比于CountDownLatch利用CAS的方法来实现应该会消耗更多的性能。不过CyclicBarrier相对于CountDownLatch的优势也是有的,可以不断循环利用的generation以及当处于休眠线程的数量达到阀值时会先执行预先定义好的任务。

CyclicBarrier 如果在await时因为中断、失败、超时等原因提前离开了屏障点,那么任务组中的其他任务将立即被中断,以InterruptedException异常离开线程。

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
项目描述:建立购物小商城平台. 实现了前台页面系统。 技术描述:通过Spring 主框架来管理Struts2和Hibernate 框架搭建的电商小平台,用MySQL数据库并创建了表有用户表,订单表,商品表,商品分类表,商品内容表,购物车表等来存储数据。用到hibernate….zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看rEADME.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 、5资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 、2项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
玉磨铁路备件智能管理系统,Vue全家桶、element-ui组件库、vue-pdf、qrcodejs2插件、canvas,该项目主要用于对于铁路设备的统计管理,主要模块包括: 1. 系统设置(仓库管理、供应商管理、用户管理) 2. 设备入库 3. 设备出库 4. 查询 5. 统….zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值