今天过生日啦,今年的生日比往年晚一些(闰了个四月)又涨一岁,29啦。
晚上闲着读会CyclickBarrier代码,中文意思翻译过来叫循环栅栏,顾名思义我们可以理解为赛马跑圈,不过有点特色就是这些赛马可以一圈又一圈的跑。并且所有马跑完再进行下一圈。
先写一个小程序把玩一下吧。
程序目的如下:
我们模拟一个学校假设有三个班级,每个班级有5个学生,同学们出了期末成绩,我们希望通过三个人(线程)帮我排序每个班学生的成绩,顺便就用冒泡做个简单实现吧。就用最粗暴的双层循环不做优化,因为优化的话其实冒泡排序还能做一些有序边界的优化,此文不做探讨,还请各位看官轻喷。
talk is cheap ,show me the code
/**
* @author guozc
*
* 2020年7月1日
*/
public class CyclicBarrierTest {
private static int[] chengji = new int[15];
static class TaskThread extends Thread {
CyclicBarrier barrier;
int beginIndex;
int endIndex;
public TaskThread(CyclicBarrier barrier, int beginIndex) {
this.barrier = barrier;
this.beginIndex = beginIndex;
this.endIndex = beginIndex + 5;
}
@Override
public void run() {
try {
for(int t = 1; t < 6; t++) {
for(int i = beginIndex; i < endIndex-1; i++) {
if (chengji[i] > chengji[i + 1]) {
int temp = chengji[i + 1];
chengji[i + 1] = chengji[i];
chengji[i] = temp;
}
}
System.out.println(Thread.currentThread().getName() +"完成了第" + t +"次排序");
barrier.await();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Random random = new Random();
for (int i = 0; i < 15; i++) {
chengji[i] = random.nextInt(50)+50;
}
System.out.println("原始成绩"+Arrays.toString(chengji));
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {
int times = 1;
@Override
public void run() {
System.out.println("执行完第" + times+"次");
System.out.println(Arrays.toString(chengji));
times++;
}
});
for(int i = 0; i < 3; i++) {
new TaskThread(cyclicBarrier,i*5).start();
}
}
}
初始化一个容量为3的循环栅栏,外加一个全班级成绩的随机数组,main方法入口一开始初始化这15个人的成绩,然后我们人为的分为三组,交给每个线程不同的分段去操作属于自己的班级成绩,通过冒泡排序进行成绩排名。为了方便演示阻塞效果我们在每次冒泡排完一次就输出一下当前线程名字并在线程内部初始化了一个统计变量让输出更加清晰
代码执行结果如下
一开始分了三组也就是结果的第一行原始成绩单,因为是随机的所以是无序的,通过每个线程撞第一次栅栏前的运算我们看到第一次执行结果,每一组的最后一个变成了最大的,依次类推完全符合冒泡排序的规律,直接看最后依次,我们看到每一组我顺序都是从小到大进行展示的。符合我们的预期。
在日常开发中,循环栅栏一般用于并发运算,比如我们稍微改一下这个场景,要求计算每个班级总分数,或者平均分数,那么久可以开启三个线程去执行,当然这么用有点小题大做了,因为计算顺序很快,数据量又小所以可能还不如单线程快。但是这部妨碍我们理解这个模型。因为这种结论本身是一个在什么场景下适合使用多线程提升效率的问题。
一般来讲,线程有效执行时间超过线程切换的时间时候基本就适合使用多线程进行并行处理。
跟CountDownLatch在这里做一个简单对比
一、昨天分析的CountDownLatch它是一个用于倒计数控制的,它侧重在不同任务的并行处理,(生产门子,生产轮胎,生产把手)最后组装成车而CyclicBarrier往往更多用在并行的相同任务的处理。
二、CountDownLatch是一次性的,而CyclicBarrier是可循环使用的,这里面有个小细节,CyclicBarrier其实是通过每个子线程去撞栅栏完成的generation的改变(这部分源码明天再补充)。所以会要求分线程执行任务比较对齐。这也跟上一点相呼应。
三、CyclicBarrier每次换代时候都会调用在初始化CyclicBarrier 传入的Runnable类型的参数,也就是源码里面的command参数。有点Map reduce的赶脚哈。这种撞栅栏的感觉有点JVM在做GC时候的saftPoint(安全点)的赶脚,不展开了有兴趣的哥们可以期待小弟的JVM相关文档,慢慢都会写一些
时间不早洗洗睡各位有不同意见还请多多留言,共同进步
源码分析待补充