JUC锁框架_CyclicBarrier原理分析

想想一下这样一个场景,有多个人需要过河,河上有一条船,船要等待满10个人才过河,过完河后每个人又各自行动。

这里的人相当于线程,注意这里,每个线程运行到一半的时候,它就要等待一个条件,即船满过河的条件,之后每个线程才能继续执行。使用CyclicBarrier就可以实现这个需求

一. CyclicBarrier介绍

CyclicBarrier表示循环屏障,它内部有个屏障数count,当调用await方法就会减少一个屏障,并让当前线程等待。当屏障数count减到0的时候,表示条件已满足,所有等待的线程应该被唤醒,并且重置屏障数count,这样就可以继续使用了。

要让线程条件等待,就想到了Condition对象,它能够实现当不满足条件时,调用await方法让当前线程等待。满足条件时调用signal或signalAll方法,唤醒等待的线程。

二. 重要成员属性

   /**
     * Generation一代的意思。
     * CyclicBarrier是可以循环使用的,用它来标志本代和下一代。
     * broken:表示本代是不是损坏了。标志有线程发生了中断,或者异常,就是任务没有完成。
     */
    private static class Generation {
        boolean broken = false;
    }

    /** 用它来实现独占锁 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 用它来实现多个线程之间相互等待通知,就是满足某些条件之后,线程才能执行,否则就等待 */
    private final Condition trip = lock.newCondition();
    /** 初始化时屏障数量 */
    private final int parties;
    /* 当条件满足(即屏障数量为0)之后,会回调这个Runnable */
    private final Runnable barrierCommand;
    /** 当前代 */
    private Generation generation = new Generation();

    // 剩余的屏障数量count。当count==0时,表示条件都满足了
    private int count;

重要属性:

  1. count: 当前剩余的屏障数量。用它来判断条件是否满足
  2. generation: 当前代。
  3. lock: 独占锁,用它来保证修改成员变量时,多线程并发安全问题。
  4. trip: Condition对象,用它来实现不满足条件时,线程等待,满足条件时,唤醒等待线程。

三. await方法

    /**
     * 减少屏障数count,
     * 如果屏障数count等于0了,条件满足,当前返回,并唤醒所有等待线程。
     * 如果屏障数不为0,那么当前线程等待。
     */
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }


    /**
     * 与await()方法作用一样,只不过添加了超时设置。
     *
     */
    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

这个方法很重要,我们就是调用这个方法,让当前线程等待着某个条件的满足的。它内部是通过调用dowait方法实现的。

四. dowait方法

   private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        // 使用lock锁,保证同一时间只有一个线程修改这些共享变量(这里就是这些成员属性)
        lock.lock();
        try {
            final Generation g = generation;

            // 表示本代已经被损坏,抛出BrokenBarrierException异常
            if (g.broken)
                throw new BrokenBarrierException();

            // 如果当前线程中断标志位true,发生了中断,表示本代CyclicBarrier也损坏了,
            // 所以调用breakBarrier方法,并抛出InterruptedException异常
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }

            // 每次调用dowait方法,表示满足了一次条件,所以count自减
            int index = --count;
            if (index == 0) {  // 表示条件都满足了,开始放行
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 开始下一代
                    nextGeneration();
                    return 0;
                } finally {
                    // 如果ranAction为false,说明command.run()方法产生异常,也表示本代损坏
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 如果index不等于0,表示条件没有满足,所以就要让当前线程等待。
            for (;;) {
                try {
                    // timed表示是否允许设置超时时间。
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        Thread.currentThread().interrupt();
                    }
                }

                /**
                 * 执行到这里说明,当前线程被唤醒了,并获取了锁。
                 * 有三种方式,都可以让线程执行到这里:
                 * 1. 有个线程被中断或者发生异常,调用breakBarrier方法,会让等待的线程执行到这里
                 * 2. 所有条件都满足,调用nextGeneration方法之后,会让等待的线程执行到这里
                 * 3. 如果等待的线程设置了超时时间,如果被超时唤醒,那么也会执行到这里
                 */


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

                if (g != generation)
                    return index;

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

这个方法是CyclicBarrier中最重要的方法。它的作用就是将当前屏障数count减一,然后判断条件是否满足(即count == 0),满足就唤醒所有等待的线程,方法返回,如果不满足,就让当前线程等待。

正常情况下,流程就是如此,但是如果运行的线程发生异常,或者等待的线程被中断唤醒了,这个就会出现问题,直接调用breakBarrier方法,表示CyclicBarrier功能失败。

    /**
     *  这个方法只在本代条件都满足(count==0)调用。
     *  1.唤醒所有等待的线程
     *  2.重置count值。
     *  3.创建新的Generation变量,表示下一代
     */
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

    /**
     * 当有一个线程发生中断或者异常的时候调用。
     * 1.将本代的broken设置为true
     * 2.重置count值。
     * 3.唤醒所有等待的线程
     */
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

nextGeneration和breakBarrier都有唤醒线程和重置屏障数count的功能,不同点就是nextGeneration设置新的generation,而breakBarrier方法会将当前generation的broken设置为true,表示被破坏掉了。

五.重要示例

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {

    public static void newThread(String name, CyclicBarrier barrier) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"等待船满过河");
                    barrier.await();
                    System.out.println("线程"+Thread.currentThread().getName()+"过完河, 各自行动");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }, name).start();
    }

    public static void main(String[] args) {
        CyclicBarrier barrier  = new CyclicBarrier(10, new Runnable() {
            @Override
            public void run() {
                System.out.println("\n在线程"+Thread.currentThread().getName()+"中 船过河了\n");
            }
        });
        for (int i = 1; i <= 10; i++) {
            newThread("t"+i, barrier);
        }
    }
}

运行结果:

线程t1等待船满过河
线程t2等待船满过河
线程t3等待船满过河
线程t4等待船满过河
线程t5等待船满过河
线程t6等待船满过河
线程t7等待船满过河
线程t8等待船满过河
线程t9等待船满过河
线程t10等待船满过河

 在线程t10中 船过河了

线程t10过完河, 各自行动
线程t1过完河, 各自行动
线程t2过完河, 各自行动
线程t3过完河, 各自行动
线程t4过完河, 各自行动
线程t5过完河, 各自行动
线程t6过完河, 各自行动
线程t7过完河, 各自行动
线程t8过完河, 各自行动
线程t9过完河, 各自行动
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容