深入浅出Java并发包—CyclicBarrier原理分析(一) (转载)

转载地址:http://yhjhappy234.blog.163.com/blog/static/316328322013514112247947/

 

CyclicBarrierCountDownLauch类似,同样也是并发包中的一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。又因为该 barrier 在释放等待线程后可以重用,所以称它为循环  barrier。(注意:CountDownLauch只能使用一次)

CyclicBarrier经常被用来模拟并发情况,许多线程协同准备后,共同访问同一个资源,模拟并发场景!我们来看一个示例:

 

final CyclicBarrier barrier = new CyclicBarrier(10);//屏障点 集结点

for(int i=0;i<10;++i){

    new Thread(){

       @Override

       public void run() {

           try {

              System.out.println(Thread.currentThread()+" is ready!");

              barrier.await();

              System.out.println(Thread.currentThread()+" is doing!");

           } catch (Exception e) {

              e.printStackTrace();

           }

       }

    }.start();

}

 

这个示例很简单,在所有线程准备好之前都处于等待阶段,一旦所以线程准备完成,所有线程一起执行(并发),这个使用前面的CountDownLauch也能实现,只不过CountDownLauch只能使用一次,而CyclicBarrier可以循环多次使用!

CyclicBarrier还有一种场景就是线程执行后等待,下面我们再来看一个示例!

 

package com.yhj.barrier;

 

import java.util.ArrayList;

import java.util.Date;

import java.util.List;

import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CyclicBarrier;

 

//工人

class Wordker extends Thread{

 

    private CyclicBarrier barrier;//集结点

    private int workNo;//工号

    private String taskName;//任务名称

 

    public Wordker(CyclicBarrier barrier,int workNo,String taskName) {

       this.barrier = barrier;

       this.workNo = workNo;

       this.taskName = taskName;

    }

 

    @Override

    public void run() {

       try {

           Thread.sleep(100);

           System.out.println(new Date()+" - YHJ"+workNo+" "+taskName+" 任务完成!");

           int index = barrier.await();//干完活等待下一批次命令

           MonitorMechine.recive(index,taskName);//通知监控平台进度

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

}

//监控器

class MonitorMechine{

 

    public static int TASK_COUNT;//任务数量

 

    //初始化监控器

    public static void init(int taskCount){

       MonitorMechine.TASK_COUNT=taskCount;

    }

 

    //接受工人传递的信息  适时发出通知

    public static void recive(int index,String taskName){

       if(index==0)

           System.out.println(new Date()+" - 监控平台:"+taskName+"任务完成!");

    }

   

    //监控 当对应批次任务完成后自动通知工人进行下一批任务操作

    public static void waitForNext(CyclicBarrier barrier,int workerCount,String oldTaskName,String newTaskName){

       try {

           recive(barrier.await(), oldTaskName);

           notice(workerCount, oldTaskName, newTaskName);

       } catch (InterruptedException e) {

           e.printStackTrace();

       } catch (BrokenBarrierException e) {

           e.printStackTrace();

       }

             

    }

   

    //监控 当对应批次任务完成后自动通知工人进行下一批任务操作

    public static void notice(int workerCount,String oldTaskName,String newTaskName){

       if(newTaskName != null)//等于NULL说明所以任务已经完成

       {

           MonitorMechine.init(workerCount);//初始化下一批次的监控信息

           System.out.println(new Date()+" - 监控平台:开始 下个任务 - "+newTaskName);

       }else

           System.out.println(new Date()+" - 监控平台:所有任务都已经完成,收工回家");

    }

 

}

//任务调度室 CyclicBarrier 测试用例总指挥

public class CyclicBarrierTestCase {

 

    //初始化任务

    public static List<String> initTaskNames(){

       List<String> taskNames = new ArrayList<String>();

       taskNames.add("组装IPod");

       taskNames.add("组装IPhone5");

       taskNames.add("组装IPad4");

       taskNames.add("组装IWatch");

       taskNames.add("组装HTC ONE");

       taskNames.add("组装SamSung GALAXY S4");

       return taskNames;

    }

 

    //启动函数

    public static void main(String[] args) {

       List<String> taskNames = initTaskNames();

       int totalWorkerCount = 10;//工人数

       int totalTaskCount = totalWorkerCount*taskNames.size();//任务数

       CyclicBarrier barrier = new CyclicBarrier(totalWorkerCount+1);//加一个Boss发令(主线程)

       MonitorMechine.init(totalWorkerCount);//初始化首批监控信息 同时执行的任务数等于工人数

       System.out.println(new Date()+" - Boss:开始干活 - "+taskNames.get(0));

       for(int i=0;i<totalTaskCount;++i){

           new Wordker(barrier,i%totalWorkerCount,taskNames.get(i/totalWorkerCount)).start();

           if((i+1)%totalWorkerCount==0)//一个批次结束后 等待主线程发令 才能继续执行下一批次指令

           {

              if(i+1<totalTaskCount)

                 MonitorMechine.waitForNext(barrier,totalWorkerCount,taskNames.get(i/totalWorkerCount),taskNames.get((i+1)/totalWorkerCount));

              else

                  MonitorMechine.waitForNext(barrier,totalWorkerCount,taskNames.get(i/totalWorkerCount),null);

           }

       }

    }

 

}

 

这个示例有点长,不过很简单,通过程序构想了这么一个事件,某工厂(看着任务是不是很像富士康哇)需要组装一些部件,6个组装任务,每个需要组装10个,程序将这60个组装任务分为6组,每组10个,并且每组执行的任务相同,只有一个任务完成后才能执行下一组任务。等对应批次的任务执行完毕后,由监控机器发出通知并安排执行下一批次任务!

上面的例子首先构建了(totalWorkerCount+1)个任务组,其中1个是为了挂起主线程,控制后面任务组的执行!每一个子任务都需要等待所以的任务执行完毕才能继续执行下一批任务!(注意,这里每一个线程包含主线程都可能成为屏障点,只有所有的线程都到达屏障点的时候,阻塞 才得以接触,后面的任务将执行)。我们来看下运行结果:

 

Fri Jun 14 20:38:20 CST 2013 - Boss:开始干活 - 组装IPod

Fri Jun 14 20:38:20 CST 2013 - YHJ2 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ4 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ6 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ8 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ0 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ3 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ5 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ7 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ9 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ1 组装IPod 任务完成!

Fri Jun 14 20:38:20 CST 2013 - 监控平台:组装IPod任务完成!

Fri Jun 14 20:38:20 CST 2013 - 监控平台:开始 下个任务 - 组装IPhone5

Fri Jun 14 20:38:20 CST 2013 - YHJ0 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ2 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ4 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ6 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ8 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ5 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ7 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ9 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ1 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - YHJ3 组装IPhone5 任务完成!

Fri Jun 14 20:38:20 CST 2013 - 监控平台:开始 下个任务 - 组装IPad4

Fri Jun 14 20:38:20 CST 2013 - 监控平台:组装IPhone5任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ1 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ3 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ5 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ7 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ9 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ8 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ0 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ2 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ4 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ6 组装IPad4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:组装IPad4任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:开始 下个任务 - 组装IWatch

Fri Jun 14 20:38:21 CST 2013 - YHJ8 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ0 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ2 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ4 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ6 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ1 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ3 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ5 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ7 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ9 组装IWatch 任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:组装IWatch任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:开始 下个任务 - 组装HTC ONE

Fri Jun 14 20:38:21 CST 2013 - YHJ0 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ2 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ4 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ6 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ8 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ1 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ3 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ5 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ7 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ9 组装HTC ONE 任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:组装HTC ONE任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:开始 下个任务 - 组装SamSung GALAXY S4

Fri Jun 14 20:38:21 CST 2013 - YHJ3 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ5 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ7 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ9 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ1 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ0 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ2 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ4 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ6 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - YHJ8 组装SamSung GALAXY S4 任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:组装SamSung GALAXY S4任务完成!

Fri Jun 14 20:38:21 CST 2013 - 监控平台:所有任务都已经完成,收工回家

 

预期和我们想想的一模一样。

从上面的示例中我们看到了CyclicBarrier有以下特点

1)、  await()方法将挂起线程,直到同组的其它线程执行完毕才能继续(这点和CountDownLauch相似)

2)、  await()方法有返回值,返回对应到达当前线程的索引,索引是从(任务数-1)开始,到0结束(CountDownLauch也是从任务数-1开始,到0结束,但是CountDownLauchawait()方法没有返回值哦)

3)、  CyclicBarrier是可以循环使用的,从名字我们就看得出来,上面的示例更能证明这一点

4)、  屏障操作不依赖于挂起的线程,那么任何线程都可以执行屏障操作。示例中的程序如果你设置断点进行跟踪的话,你会发现不一定是主线程的waitForNext中的await触发下一组操作,任何一个线程只要到达了屏障点都可能触发这一操作。

5)、  CyclicBarrier 的构造函数允许携带一个任务,这个任务将在0%屏障点执行,它将在await()==0后执行。

6)、  CyclicBarrier 如果在await时因为中断、失败、超时等原因提前离开了屏障点,那么任务组中的其他任务将立即被中断,以InterruptedException异常离开线程。

7)、  对于失败的同步尝试,CyclicBarrier 使用了一种要么全部要么全不 (all-or-none) 的破坏模式:如果因为中断、失败或者超时等原因,导致线程过早地离开了屏障点,那么在该屏障点等待的其他所有线程也将通过BrokenBarrierException(如果它们几乎同时被中断,则用InterruptedException)以反常的方式离开。

当然这些特点的前提有一个比较重要的happen-before原则:

内存一致性效果:线程中调用 await() 之前的操作 happen-before 那些是屏障操作的一部份的操作,后者依次 happen-before 紧跟在从另一个线程中对应await() 成功返回的操作。

了解了他的使用场景之后,下面就进入我们今天的正题,这玩意究竟是怎么实现的呢?我们来逐一揭秘:

posted @ 2016-04-03 21:06  青青子衿J  阅读(168)  评论(0)    收藏  举报