摘要:序包里有几个能帮助人们管理相互合作的线程集的类,为多线程常见的应用场景预置了抽象好的类库。如果没报错就更新屏障状态并唤醒所有线程继续执行。如果还有未到达的线程,就进入一个死循环,直到超时线程中断屏障失效全部完成等情况下退出。
我的博客 转载请注明原创出处。序
java.util.concurrent包里有几个能帮助人们管理相互合作的线程集的类,为多线程常见的应用场景预置了抽象好的类库。在遇到这些应用场景时应该直接重用合适的库类而不要试图提供手工的锁与条件的集合。
同步屏障 CyclicBarrier官方定义上文已经给出,人话版是等待特定数量的线程都到达同步屏障后各线程才继续执行。
同步屏障有两个构造函数,第一个构造函数只需要指定需要等待的线程数量,第二构造函数多了一个在特定数量的线程都到达同步屏障时优先执行的Runnable。
例子:
public class CyclicBarrierTest { // 等待4个线程到达同步屏障,全部到达后优先执行一个 Runnable private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> System.out.println("全部到达同步屏障" + LocalDateTime.now())); public static void main(String[] args) throws InterruptedException, BrokenBarrierException { Runnable runnable = () -> { System.out.println("到达同步屏障" + LocalDateTime.now()); try { cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("继续执行"); }; Listlist = Arrays.asList(runnable, runnable, runnable); list.forEach(runnable1 -> new Thread(runnable1).start()); Thread.sleep(1000); System.out.println("最后一个线程到达同步屏障"); cyclicBarrier.await(); } } 输出: 到达同步屏障2018-08-12T14:33:16.769 到达同步屏障2018-08-12T14:33:16.769 到达同步屏障2018-08-12T14:33:16.769 最后一个线程到达同步屏障 全部到达同步屏障2018-08-12T14:33:17.746 继续执行 继续执行 继续执行 Process finished with exit code 0
同步屏障的应用场景是那种多线程执行任务,在全部任务执行完成后需要进行一些操作的场景。比如对每个用户进行充值统计,最后汇总返回。
CyclicBarrier的方法如上,分别是
getParties() 返回需要到达同步屏障的线程数量 await() 等待所有线程到达 await(long, TimeUnit) 带时间限制的await() isBroken() 判断阻塞的线程是否被中断 reset() 重置计数器 getNumberWaiting() 当前被阻塞的线程数量,该方法主要用于调试和断言源码分析
那么CyclicBarrier是怎么实现这个效果的呢?我们从最常用的await()方法入手。
可以看到await()方法主要是调用了CyclicBarrier私有的dowait()方法
如注释所言,dowait()方法就是实现功能的主要方法了。
首先拿到可重入的锁
然后通过内部类Generation判断阻塞的线程是否被中断或该屏障已经失效。
如果线程没有被中断,那么就获取还没有到达的线程数量并减一。如果已经没有需要等待的线程了,就判断是否有需要执行的Runnable。如果没报错就更新屏障状态并唤醒所有线程继续执行。Runnable执行报错的话执行breakBarrier()方法。
如果还有未到达的线程,就进入一个死循环,直到超时、线程中断、屏障失效、全部完成等情况下退出。
完整的代码:
/** * Each use of the barrier is represented as a generation instance. * The generation changes whenever the barrier is tripped, or * is reset. There can be many generations associated with threads * using the barrier - due to the non-deterministic way the lock * may be allocated to waiting threads - but only one of these * can be active at a time (the one to which {@code count} applies) * and all the rest are either broken or tripped. * There need not be an active generation if there has been a break * but no subsequent reset. */ private static class Generation { boolean broken = false; } /** The lock for guarding barrier entry */ private final ReentrantLock lock = new ReentrantLock(); /** Condition to wait on until tripped */ private final Condition trip = lock.newCondition(); /** The number of parties */ private final int parties; /* The command to run when tripped */ private final Runnable barrierCommand; /** The current generation */ private Generation generation = new Generation(); /** * Number of parties still waiting. Counts down from parties to 0 * on each generation. It is reset to parties on each new * generation or when broken. */ private int count; /** * Updates state on barrier trip and wakes up everyone. * Called only while holding lock. */ private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); } /** * Sets current barrier generation as broken and wakes up everyone. * Called only while holding lock. */ private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); } /** * Main barrier code, covering the various policies. */ 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(); } 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(); } }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/71766.html
摘要:将屏障重置为其初始状态。注意,在由于其他原因造成损坏之后,实行重置可能会变得很复杂此时需要使用其他方式重新同步线程,并选择其中一个线程来执行重置。 安全共享对象策略 1.线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改2.共享只读 : 一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它3.线程安全对象 : 一个线程安全...
摘要:当到达栅栏后,由于没有满足总数的要求,所以会一直等待,当线程到达后,栅栏才会放行。任务其实就是当最后一个线程到达栅栏时,后续立即要执行的任务。 showImg(https://segmentfault.com/img/remote/1460000016010958); 本文首发于一世流云专栏:https://segmentfault.com/blog... 一、CyclicBarri...
摘要:当位玩家角色都选择完毕后,开始进入游戏。进入游戏时需要加载相关的数据,待全部玩家都加载完毕后正式开始游戏。 showImg(https://segmentfault.com/img/remote/1460000016414941?w=640&h=338); CyclicBarrier是java.util.concurrent包下面的一个工具类,字面意思是可循环使用(Cyclic)的屏障...
摘要:前言之前学多线程的时候没有学习线程的同步工具类辅助类。而其它线程完成自己的操作后,调用使计数器减。信号量控制一组线程同时执行。 前言 之前学多线程的时候没有学习线程的同步工具类(辅助类)。ps:当时觉得暂时用不上,认为是挺高深的知识点就没去管了.. 在前几天,朋友发了一篇比较好的Semaphore文章过来,然后在浏览博客的时候又发现面试还会考,那还是挺重要的知识点。于是花了点时间去了解...
摘要:在创建对象时,需要转入一个值,用于初始化的成员变量,该成员变量表示屏障拦截的线程数。当到达屏障的线程数小于时,这些线程都会被阻塞住。当所有线程到达屏障后,将会被更新,表示进入新一轮的运行轮次中。 1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个...
阅读 1012·2021-11-18 10:02
阅读 1259·2021-09-23 11:22
阅读 2544·2021-08-21 14:08
阅读 1603·2019-08-30 15:55
阅读 1682·2019-08-30 13:45
阅读 3067·2019-08-29 16:52
阅读 3059·2019-08-29 12:18
阅读 1603·2019-08-26 13:36