透过案例--了解循环栅栏

循环栅栏

    • 循环栅栏
      • 一、案例-司令跟士兵的日常
        • 1、上demo
          • 1)、代码实现
          • 2)、测试结果以及小结
            • ①测试结果导读
            • ②小结:
            • ③外层线程最后调用循环栅栏,还能否起到阻塞作用?
        • 2、草图导读
        • 3、番外篇
      • 案例-游乐场之行~更直白

循环栅栏

一、案例-司令跟士兵的日常

别的线程和循环栅栏这俩货要合作,所以他们先进行了简单的沟通:

别的线程:一听栅栏,肯定就是有限制,据说这里的限制是阻塞我(别的线程)的,而且阻塞的个数由你(循环栅栏)来定
(假定阻塞个数是7,那么这里循环栅栏的计数器大小为7)

循环栅栏:是的。

别的线程:哦哦,那要是阻塞我(别的线程)的个数超过7个,是不是就让我(别的线程)执行了?

循环栅栏:是的。但是这里讲的案例,既然有了我(循环栅栏)的参与,那你就要在你的run里取调用我,调用顺序看你的需要。只要你调用我了,不管怎样的调用顺序,我都会起到阻塞的作用;而且你(别的线程)可以重复使用我(循环栅栏);阻塞的计数器个数由我(循环栅栏)来定,比如设定为3,那就阻塞你(别的线程)线程的个数达到3的时候,才会调用你(别的线程)的run方法。
 
别的线程:好的,谢谢~

上面总结:
1、循环栅栏对象可以重复使用;
2、循环栅栏计数器的值决定阻塞其他线程的个数,达到计数器个数,被阻塞的线程可以继续运行;
3、只要线程的run里调用了循环栅栏,不论执行顺序,都有阻塞的效果。
4、用法:在线程的run方法内,调用循环栅栏包裹的线程。

针对对话内容,进行了测试
在代码中,循环栅栏的用法就是线程内调用栅栏,栅栏起到阻塞的作用

1、上demo

1)、代码实现

注释掉了原来代码打印,使循环栅栏的使用情况更加清晰;

package com.test.threadStu;

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

public class CyclicBarrierDemo {
    public static class Soldier implements  Runnable{
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(CyclicBarrier cyclic,String soldierName){
            this.cyclic=cyclic;
            this.soldier=soldierName;
        }
        public void run(){
            try {
                //等待所有士兵到齐
                cyclic.await();
                doWork();
                //等待所有士兵完成工作
                cyclic.await();
            }catch (InterruptedException e){
                e.printStackTrace();
            }catch (BrokenBarrierException e){
                e.printStackTrace();
            }

        }

        private void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt()%10000));
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
            System.out.println(soldier+":任务完成-----step2:外层线程调用的工作任务");
        }
    }

    public static class BarrierRun implements Runnable{
        boolean flag;
        int N;
        public BarrierRun(boolean flag,int N){
            this.flag=flag;
            this.N=N;
        }
        public void run(){
            if(flag){
                System.out.println("司令:[士兵"+N+"个,任务完成]-----step3:循环栅栏包裹的线程任务");
            }else{
                System.out.println("司令:[士兵"+N+"个,集合完毕]-----step1:循环栅栏包裹的线程任务");
                flag=true;
            }
        }
    }

    public static void main(String[] args) {
        final  int N=5;
        Thread[] allSoldier=new Thread[N];
        boolean flag=false;
        CyclicBarrier cyclic=new CyclicBarrier(N,new BarrierRun(flag,N));
        //设置屏障点,主要是为了执行这个方法
       // System.out.println("集合队伍!");
        for (int i=1;i<=N;i++){
       //     System.out.println("士兵"+i+"报道!");
            allSoldier[i-1]=new Thread(new Soldier(cyclic,"士兵"+i));
            allSoldier[i-1].start();
          /*  if(i==3){//标识1:调用线程终端
                allSoldier[i].interrupt();
            }*/
        }
        System.out.println("上述step123为外层线程run()方法的调用顺序");
    }
}

2)、测试结果以及小结
①测试结果导读

执行顺序可按实际需求来,这里执行顺序

step1: 调用栅栏 wait,意味着先阻塞Soldier线程5个数,然后执行循环栅栏包裹的线程任务【栅栏包裹的任务执行完,接着指定外层线程的核心任务】

step2: 调用线程Soldier的工作任务

step3: 调用栅栏 await ,由于123步属于线程Soldier的一次调度,而第一步已经阻塞过了,这里直接执行栅栏里包裹的线程任务。

使用当中 以上步骤1是栅栏里的,await过程按顺序干俩事:① 阻塞Soldier为栅栏计数器个数5 ②、在1之后, 执行栅栏里包裹的线程任务

上述step123为外层线程run()方法的调用顺序
司令:[士兵5,集合完毕]-----step1:循环栅栏包裹的线程任务
士兵4:任务完成-----step2:外层线程调用的工作任务
士兵1:任务完成-----step2:外层线程调用的工作任务
士兵2:任务完成-----step2:外层线程调用的工作任务
士兵3:任务完成-----step2:外层线程调用的工作任务
士兵5:任务完成-----step2:外层线程调用的工作任务
司令:[士兵5,任务完成]-----step3:循环栅栏包裹的线程任务
②小结:

1、在Soldier 工作任务里调度BarrierRun的线程,Soldier的线程启动触发BarrierRun启动

2、在Soldier【外层线程】里BarrierRun【循环栅栏包裹的线程】在什么时候调度看你实际需要

3、以上step步骤执行的顺序不是固定的,可根据你的实际需要执行

③外层线程最后调用循环栅栏,还能否起到阻塞作用?

答案是肯定的,稍加改动代码,
把 main里的【士兵+i+报道!】注释释放掉,
线程内循环栅栏的调用顺序,看是否还能起到阻塞的作用
透过案例--了解循环栅栏_第1张图片
看这个答案也知道,仍然起到了阻塞的作用。

士兵1报道!
士兵2报道!
士兵3报道!
士兵4报道!
士兵5报道!
上述step123为外层线程run()方法的调用顺序
士兵1:任务完成-----step2:外层线程调用的工作任务
士兵3:任务完成-----step2:外层线程调用的工作任务
士兵2:任务完成-----step2:外层线程调用的工作任务
士兵5:任务完成-----step2:外层线程调用的工作任务
士兵4:任务完成-----step2:外层线程调用的工作任务
司令:[士兵5,集合完毕]-----step1:循环栅栏包裹的线程任务

2、草图导读

如果你经过一番纠结还是不太明白,可以参看下面的草图。
关于前置变量有问号的小伙伴从第二个图里找含义:
透过案例--了解循环栅栏_第2张图片

前置变量:
A:Soldier【外层线程】
b:BarrierRun【循环栅栏包裹的线程】
透过案例--了解循环栅栏_第3张图片
那么问题来了:
循环栅栏的使用特点:是用在一个线程的工作任务里的吗?我觉得是的,首先栅栏就是阻塞的作用,那不在被阻塞的线程里工作,他咋阻塞人家呀,您说是不?

在下才疏学浅,关于这个问题的解答,仅代表我个人观点,不一定那么回事儿,欢迎反驳~

3、番外篇

案例-士兵跟司令的日常里,code里有两个异常,可以了解下

  • InterruptedException:表示等待过程中,线程被中断,这是一个非常通用的异常。大部分迫使线程等待的方法,都可能会抛出这个异常,使线程在等待时依然可以响应外部紧急事件
  • BrokenBarrierException: 这个异常则是CyclicBarrier 特有的,表示当前CyclicBarrier 已经破损,系统没法等所有线程到齐了。如果继续等待可能是徒劳无功的,因此还是就地散伙,打道回府吧。如果放开主程序中"标识1"的三行代码
  if(i==3){//标识1:调用线程终端
       allSoldier[0].interrupt();
   } 

透过案例--了解循环栅栏_第4张图片

案例-游乐场之行~更直白

时间有限,后续会补充追加,在此标记备注

比如你去游乐场玩儿一个娱乐项目,结果每次都要排队,5人一组,才能一起玩儿一次。
循环栅栏也是一样,栅栏就是加限制,进入栅栏内才能做一些事,上述案例,栅栏的计数器是5,就是凑齐5个人,这个项目才能玩儿。

在代码中,循环栅栏的用法就是线程内调用栅栏,栅栏起到阻塞的作用,直到线程数A达到5。才会执行栅栏内要做的事(C)
栅栏的实例化需要另外一个线程(玩儿娱乐项目),有人来排队(执行外层线程A),栅栏会阻塞,直到阻塞个数达到栅栏计数器个数,

参考文献:
案例-司令跟士兵的日常-----《实战java高并发程序设计》

你可能感兴趣的:(线程池,java基础)