别的线程和循环栅栏这俩货要合作,所以他们先进行了简单的沟通:
别的线程:一听栅栏,肯定就是有限制,据说这里的限制是阻塞我(别的线程)的,而且阻塞的个数由你(循环栅栏)来定
(假定阻塞个数是7,那么这里循环栅栏的计数器大小为7)
循环栅栏:是的。
别的线程:哦哦,那要是阻塞我(别的线程)的个数超过7个,是不是就让我(别的线程)执行了?
循环栅栏:是的。但是这里讲的案例,既然有了我(循环栅栏)的参与,那你就要在你的run里取调用我,调用顺序看你的需要。只要你调用我了,不管怎样的调用顺序,我都会起到阻塞的作用;而且你(别的线程)可以重复使用我(循环栅栏);阻塞的计数器个数由我(循环栅栏)来定,比如设定为3,那就阻塞你(别的线程)线程的个数达到3的时候,才会调用你(别的线程)的run方法。
别的线程:好的,谢谢~
上面总结:
1、循环栅栏对象可以重复使用;
2、循环栅栏计数器的值决定阻塞其他线程的个数,达到计数器个数,被阻塞的线程可以继续运行;
3、只要线程的run里调用了循环栅栏,不论执行顺序,都有阻塞的效果。
4、用法:在线程的run方法内,调用循环栅栏包裹的线程。
针对对话内容,进行了测试
在代码中,循环栅栏的用法就是线程内调用栅栏,栅栏起到阻塞的作用
注释掉了原来代码打印,使循环栅栏的使用情况更加清晰;
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()方法的调用顺序");
}
}
执行顺序可按实际需求来,这里执行顺序
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报道!
士兵2报道!
士兵3报道!
士兵4报道!
士兵5报道!
上述step123为外层线程run()方法的调用顺序
士兵1:任务完成-----step2:外层线程调用的工作任务
士兵3:任务完成-----step2:外层线程调用的工作任务
士兵2:任务完成-----step2:外层线程调用的工作任务
士兵5:任务完成-----step2:外层线程调用的工作任务
士兵4:任务完成-----step2:外层线程调用的工作任务
司令:[士兵5个,集合完毕]-----step1:循环栅栏包裹的线程任务
如果你经过一番纠结还是不太明白,可以参看下面的草图。
关于前置变量有问号的小伙伴从第二个图里找含义:
前置变量:
A:Soldier【外层线程】
b:BarrierRun【循环栅栏包裹的线程】
那么问题来了:
循环栅栏的使用特点:是用在一个线程的工作任务里的吗?我觉得是的,首先栅栏就是阻塞的作用,那不在被阻塞的线程里工作,他咋阻塞人家呀,您说是不?
在下才疏学浅,关于这个问题的解答,仅代表我个人观点,不一定那么回事儿,欢迎反驳~
案例-士兵跟司令的日常里,code里有两个异常,可以了解下
if(i==3){//标识1:调用线程终端
allSoldier[0].interrupt();
}
时间有限,后续会补充追加,在此标记备注
比如你去游乐场玩儿一个娱乐项目,结果每次都要排队,5人一组,才能一起玩儿一次。
循环栅栏也是一样,栅栏就是加限制,进入栅栏内才能做一些事,上述案例,栅栏的计数器是5,就是凑齐5个人,这个项目才能玩儿。
在代码中,循环栅栏的用法就是线程内调用栅栏,栅栏起到阻塞的作用,直到线程数A达到5。才会执行栅栏内要做的事(C)
栅栏的实例化需要另外一个线程(玩儿娱乐项目),有人来排队(执行外层线程A),栅栏会阻塞,直到阻塞个数达到栅栏计数器个数,
参考文献:
案例-司令跟士兵的日常-----《实战java高并发程序设计》