CyclicBarrier源码分析

由一个笑话引发的一些思考:

据说是一个月薪 9K 的 Java 程序员,因老板让他写一个排序算法,然后他就写了一段屌炸天的休眠排序算法,接着他就被老板开除了……算法长这样:

CyclicBarrier源码分析_第1张图片

 

突然想到的问题时,由于你的线程是一个一个开的,它们开始执行的时间不一样,如果数字都是很近的话,排出来的顺序可能都是不一致的。然后突然就想到了CyclicBarrier……然后就想知道下它里面是怎么保证多个线程在同一个起点开始跑的。

CyclicBarrier做的事情是,让一组线程到达一个屏障(也可以叫同步点-Barrier)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。而之所以叫Cyclic,是因为所有等待线程被释放后,CyclicBarrier可以被重用

CyclicBarrier常用于线程组内部想成等待的问题(例如多线程分组计算,猜测fork/join里面也用到了)。

 

以我编写的代码为例,进行一次分析,代码如下:

public class useCyclicBarrier {

    private static final AtomicInteger value = new AtomicInteger(0);

    private static final CyclicBarrier c = new CyclicBarrier(4, new Runnable() {
        @Override
        public void run() {
            System.out.println("now all Thread reach the same point");
        }
    });


    public static void main(String[] args) {
        test1();
    }


    /**
     * @Description: 理解它的循环
     * @param:
     * @return:
     * @author:
     * @Date: 2018/11/14
     */
    private static void test1() {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(4, 10,
                60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());


        for (int i=0;i<4;i++){
            pool.submit(new GoThread());
        }

        pool.shutdown();


    }

    private static class GoThread implements Runnable {

        @Override
        public void run() {
            int threadName = value.incrementAndGet();
            System.out.println("start execute Thread " + threadName + " " + System.currentTimeMillis());
            try {
                TimeUnit.SECONDS.sleep(threadName);//睡眠不同的时间
                System.out.println("start sleep");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                c.await();//拦住4条线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("end at the same time" + System.currentTimeMillis());//然后再同一时间结束



        }
    }


}

1.首先,在IDEA中让4条线程同时阻塞在await()方法这里(带有参数的await方法暂时不分析)。然后让第一个线程继续执行下去。

CyclicBarrier源码分析_第2张图片

2.接着第一条线程会对当前代码加锁,这样是为了保证线程安全性。

CyclicBarrier源码分析_第3张图片

其中generation但是用于判断当前的Barrier是否是有效的,如果当前Barrier被broken了,那么之后的线程都会检测到这一信息,然后越过当前Barrier并抛出一个异常

如果当前线程interrupt,那么当前线程充当broken Barrier的角色

CyclicBarrier源码分析_第4张图片

总之就是如果Barrier失效了,所有的线程都会越过这个Barrier

 

3.接着第一条线程会执行—count,意思就是说,等待的线程又达到了一个。如果此时count不为0,说明还有线程未达到Barrier,那么就调用trip.await()进入阻塞

CyclicBarrier源码分析_第5张图片

trip.await()是AQS类中的一个方法,它会释放当前持有的锁然后进入阻塞状态释放的目的是让第二个线程可以进行—count操作

CyclicBarrier源码分析_第6张图片

4.然后第二条和第三条线程来了,做了和第一条线程一样的操作,也成功阻塞了……

5.现在是最后一条线程来了,注意此时的count此时已经变成0了,也就是说所有线程都到达了Barrier点。

CyclicBarrier源码分析_第7张图片

接着就执行构造函数中设置的任务,调用它的run()方法,run()就不用新开线程了,流弊流弊。

然后调用nextGeneration(),就是实现它的Cyclic功能,看下它里面是做了哪些操作:

CyclicBarrier源码分析_第8张图片

该方法内部会唤醒所有阻塞的线程,是的它们可以继续执行。并且会重置下count和generation,真正的实现Cyclic,等待下一次的调用。

唤醒完所有线程之后,当前线程就可以return了,所有线程也就可以继续执行了,至此,我们简单的分析它的功能就结束了。

 

回到初始点,其实好像也并没有解决一起开始的问题,因为接下来还是走的线程调度的方法,一开始的那个排序算法差不多也是同一时间执行的,只是start线程需要点时间而已,不管怎么说,总算了解了CyclicBarrier的内部原理,也是蛮有收货的。

 

 

 

 

 

 

你可能感兴趣的:(CyclicBarrier源码分析)