JUC并发工具包之CyclicBarrier

1、简介

CyclicBarrier是一个同步器,允许多个线程等待彼此直到达一个执行点(barrier)。

CyclicBarrier都是在多个线程必须等到彼此都到达同一个执行点后才执行一段逻辑时才被使用。

barrier被叫做cyclic是因为阻塞线程恢复后可以重复使用barrier

2、使用

CyclicBarrier的构造器很简单,传入一个整形表示线程数再调用barrier实例的await()方法表示所有线程要到达这一共同的执行点。

public CyclicBarrier(int parties)

所有需要同步执行的线程也被叫做parties,调用await()方法可以注册一个线程到达过执行点(barrier point)。

这个调用是同步的并且当前线程会挂起直至其他线程都调用到barrier的await()方法,该场景中既定数量的线程都调用了await()方法叫做tripping the barrier(源码中这种描述出现多次)

或者,我们也可以传第二个参数给构造方法,这是一个Runnable的实例,这将作为最后一个线程trips the barrier

public CyclicBarrier(int parties, Runnable barrierAction)

3、实现

为了实际使用一下CyclicBarrier,我们来考虑下面一个场景:

固定数量的线程执行一段操作并把对应的结果存到列表中,当所有的线程执行完操作,其中一个(也就是最后一个trips the barrier的线程)开始处理其他线程获取到的结果。

我们开始实现一下这个类:

public class CyclicBarrierDemo {
 
    private CyclicBarrier cyclicBarrier;
    private List<List<Integer>> partialResults
     = Collections.synchronizedList(new ArrayList<>());
    private Random random = new Random();
    private int NUM_PARTIAL_RESULTS;
    private int NUM_WORKERS;
 
    // ...
}

这个类已经很直接了当了,NUM_WORKERS是线程数,NUM_PARTIAL_RESULTS是每个线程即将要输出结果的数量

最后,我们定义一个partialResults来存储所有线程存储的结果,请注意这个列表是一个SynchronizedList,因为多个线程会同时向它写入数据(普通ArrayList的add方法不是线程安全的)

现在我们实现一下每个工作线程的逻辑:

public class CyclicBarrierDemo {
 
    // ...
 
    class NumberCruncherThread implements Runnable {
 
        @Override
        public void run() {
            String thisThreadName = Thread.currentThread().getName();
            List<Integer> partialResult = new ArrayList<>();
 
            // Crunch some numbers and store the partial result
            for (int i = 0; i < NUM_PARTIAL_RESULTS; i++) {    
                Integer num = random.nextInt(10);
                System.out.println(thisThreadName
                  + ": Crunching some numbers! Final result - " + num);
                partialResult.add(num);
            }
 
            partialResults.add(partialResult);
            try {
                System.out.println(thisThreadName 
                  + " waiting for others to reach barrier.");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                // ...
            } catch (BrokenBarrierException e) {
                // ...
            }
        }
    }
}

我们现在实现所有现在都到执行点后的逻辑,为了把逻辑写简单点,我们就把partialResults列表中的值全部相加。

public class CyclicBarrierDemo {
 
    // ...
     
    class AggregatorThread implements Runnable {
 
        @Override
        public void run() {
 
            String thisThreadName = Thread.currentThread().getName();
 
            System.out.println(
              thisThreadName + ": Computing sum of " + NUM_WORKERS 
              + " workers, having " + NUM_PARTIAL_RESULTS + " results each.");
            int sum = 0;
 
            for (List<Integer> threadResult : partialResults) {
                System.out.print("Adding ");
                for (Integer partialResult : threadResult) {
                    System.out.print(partialResult+" ");
                    sum += partialResult;
                }
                System.out.println();
            }
            System.out.println(thisThreadName + ": Final result = " + sum);
        }
    }
}

那现在最后一步就是构建CyclicBarrier并把所有的逻辑在main()方法中跑通:

public class CyclicBarrierDemo {
 
    // Previous code
  
    public void runSimulation(int numWorkers, int numberOfPartialResults) {
        NUM_PARTIAL_RESULTS = numberOfPartialResults;
        NUM_WORKERS = numWorkers;
 
        cyclicBarrier = new CyclicBarrier(NUM_WORKERS, new AggregatorThread());
 
        System.out.println("Spawning " + NUM_WORKERS
          + " worker threads to compute "
          + NUM_PARTIAL_RESULTS + " partial results each");
  
        for (int i = 0; i < NUM_WORKERS; i++) {
            Thread worker = new Thread(new NumberCruncherThread());
            worker.setName("Thread " + i);
            worker.start();
        }
    }
 
    public static void main(String[] args) {
        CyclicBarrierDemo demo = new CyclicBarrierDemo();
        demo.runSimulation(5, 3);
    }
}

以上代码中,我们在CyclicBarrier中初始化了5个线程,每个线程会输出3个整形并存储到partialResults中。

4、输出结果

以下就是上述程序运行一次的输出结果,每次执行可能都会有不同的结果因为线程会以不同的顺序执行。

Spawning 5 worker threads to compute 3 partial results each
Thread 0: Crunching some numbers! Final result - 6
Thread 0: Crunching some numbers! Final result - 2
Thread 0: Crunching some numbers! Final result - 2
Thread 0 waiting for others to reach barrier.
Thread 1: Crunching some numbers! Final result - 2
Thread 1: Crunching some numbers! Final result - 0
Thread 1: Crunching some numbers! Final result - 5
Thread 1 waiting for others to reach barrier.
Thread 3: Crunching some numbers! Final result - 6
Thread 3: Crunching some numbers! Final result - 4
Thread 3: Crunching some numbers! Final result - 0
Thread 3 waiting for others to reach barrier.
Thread 2: Crunching some numbers! Final result - 1
Thread 2: Crunching some numbers! Final result - 1
Thread 2: Crunching some numbers! Final result - 0
Thread 2 waiting for others to reach barrier.
Thread 4: Crunching some numbers! Final result - 9
Thread 4: Crunching some numbers! Final result - 3
Thread 4: Crunching some numbers! Final result - 5
Thread 4 waiting for others to reach barrier.
Thread 4: Computing final sum of 5 workers, having 3 results each.
Adding 6 2 2 
Adding 2 0 5 
Adding 6 4 0 
Adding 1 1 0 
Adding 9 3 5 
Thread 4: Final result = 46

5、总结

  • 能看到CyclicBarrier的适用场景
  • 实现了一段逻辑:固定线程都到达一个执行点后再执行其他逻辑

最后这些例子的实现都可以在Github上找到

原文地址:https://www.baeldung.com/java-cyclic-barrier

6、思考

前文说过

barrier被叫做cyclic是因为阻塞线程恢复后可以重复使用barrier

那这里的重复使用怎么理解呢?

最后可以看下王者荣耀的例子,十分帮忙理解CyclicBarrier。

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