【Java并发编程实战】—–“J.U.C”:CyclicBarrier

          在上篇博客中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier。在JDK API中是这么介绍的:

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

CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

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

CyclicBarrier分析

CyclicBarrier结构如下:

【Java并发编程实战】—–“J.U.C”:CyclicBarrier_第1张图片

 

从上图可以看到CyclicBarrier内部使用ReentrantLock独占锁实现的。其构造函数如下:

CyclicBarrier(int parties):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。

CyclicBarrier(int parties, Runnable barrierAction):创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

[java] view plain copy print ?
  1. public CyclicBarrier(int parties) {  
  2.         this(parties, null);  
  3.     }  
  4.       
  5.     public CyclicBarrier(int parties, Runnable barrierAction) {  
  6.         if (parties <= 0throw new IllegalArgumentException();  
  7.         this.parties = parties;  
  8.         this.count = parties;  
  9.         this.barrierCommand = barrierAction;  
  10.     }  

在CyclicBarrier中,最重要的方法就是await(),在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。其源代码如下:

[java] view plain copy print ?
  1. public int await() throws InterruptedException, BrokenBarrierException {  
  2.         try {  
  3.             return dowait(false, 0L);  
  4.         } catch (TimeoutException toe) {  
  5.             throw new Error(toe); // cannot happen;  
  6.         }  
  7.     }  

await内部调用dowait():

[java] view plain copy print ?
  1. private int dowait(boolean timed, long nanos)  
  2.             throws InterruptedException, BrokenBarrierException,  
  3.                    TimeoutException {  
  4.             //独占锁  
  5.             final ReentrantLock lock = this.lock;  
  6.             //获取独占锁  
  7.             lock.lock();  
  8.             try {  
  9.                 //保存当前"Generation"  
  10.                 final Generation g = generation;  
  11.                 //当前generation“已损坏”,抛出BrokenBarrierException异常  
  12.                 //抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrier  
  13.                 if (g.broken)  
  14.                     throw new BrokenBarrierException();  
  15.   
  16.                 //当前线程中断,通过breakBarrier终止终止CyclicBarrier  
  17.                 if (Thread.interrupted()) {  
  18.                     breakBarrier();  
  19.                     throw new InterruptedException();  
  20.                 }  
  21.                  
  22.                //计数器-1  
  23.                int index = --count;  
  24.                //如果计数器 == 0  
  25.                //表示所有线程都已经到位,触发动作(是否执行某项任务)  
  26.                if (index == 0) {  // tripped  
  27.                    boolean ranAction = false;  
  28.                    try {  
  29.                        //barrierCommand线程要执行的任务  
  30.                        final Runnable command = barrierCommand;  
  31.                        //执行的任务!=null,执行任务  
  32.                        if (command != null)  
  33.                            command.run();  
  34.                        ranAction = true;  
  35.                        //唤醒所有等待线程,并更新generation。  
  36.                        nextGeneration();  
  37.                        return 0;  
  38.                    } finally {  
  39.                        if (!ranAction)  
  40.                            breakBarrier();  
  41.                    }  
  42.                }  
  43.   
  44.                //循环一直执行,直到下面三个if一个条件满足才会退出循环  
  45.                for (;;) {  
  46.                     try {  
  47.                         //如果不是超时等待,则调用await等待  
  48.                         if (!timed)  
  49.                             trip.await();  
  50.                         //调用awaitNanos等待  
  51.                         else if (nanos > 0L)  
  52.                             nanos = trip.awaitNanos(nanos);  
  53.                     } catch (InterruptedException ie) {  
  54.                         //  
  55.                         if (g == generation && ! g.broken) {  
  56.                             breakBarrier();  
  57.                             throw ie;  
  58.                         } else {  
  59.                             Thread.currentThread().interrupt();  
  60.                         }  
  61.                     }  
  62.   
  63.                     //当前generation“已损坏”,抛出BrokenBarrierException异常  
  64.                     //抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrier  
  65.                     if (g.broken)  
  66.                         throw new BrokenBarrierException();  
  67.   
  68.                     //generation已经更新,返回index  
  69.                     if (g != generation)  
  70.                         return index;  
  71.   
  72.                     //“超时等待”,并且时间已到,则通过breakBarrier()终止CyclicBarrier  
  73.                     if (timed && nanos <= 0L) {  
  74.                         breakBarrier();  
  75.                         throw new TimeoutException();  
  76.                     }  
  77.                 }  
  78.             } finally {  
  79.                 //释放独占锁  
  80.                 lock.unlock();  
  81.             }  
  82.         }  

在dowait方法中其实处理逻辑还是比较简单的:

1、首先判断该barrier是否已经断开了,如果断开则抛出BrokenBarrierException异常;

2、判断计算器index是否等于0,如果等于0,则表示所有的线程准备就绪,已经到达某个公共屏障点了,barrier可以进行后续工作了(是否执行某项任务(构造函数决定));然后调用nextGeneration方法进行更新换代工作(其中会唤醒所有等待的线程);

3、通过for循环(for(;;))使线程一直处于等待状态。直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生。

在dowait中有Generation这样一个对象。该对象是CyclicBarrier的一个成员变量:

[java] view plain copy print ?
  1. private static class Generation {  
  2.         boolean broken = false;  
  3.     }  

Generation描述着CyclicBarrier的更显换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。

对于中断,CyclicBarrier是通过breakBarrier()实现的:

[java] view plain copy print ?
  1. private void breakBarrier() {  
  2.         generation.broken = true;  
  3.         count = parties;  
  4.         trip.signalAll();  
  5.     }  

在breakBarrier()中除了将broken设置为true,还会调用signalAll将在CyclicBarrier处于等待状态的线程全部唤醒。

在超时的判断中,CyclicBarrier根据timed的值来执行不同的wait。await、awaitNanos都是Condition中的方法。

当index = –count等于0时,标识“有parties个线程到达barrier”,临界条件到达,则执行相应的动作。执行完动作后,则调用nextGeneration进行更新换代:

[java] view plain copy print ?
  1. private void nextGeneration() {  
  2.         //唤醒所有处于等待状态的线程  
  3.         trip.signalAll();  
  4.         //初始化计数器  
  5.         count = parties;  
  6.         //产生新的Generation对象  
  7.         generation = new Generation();  
  8.     }  

示例

1、线程等待到一定条件后才会继续进行。

[java] view plain copy print ?
  1. public class CyclicBarrierTest_1 {  
  2.     private static CyclicBarrier barrier;  
  3.       
  4.     static class threadTest1 extends Thread{  
  5.         public void run() {  
  6.             System.out.println(Thread.currentThread().getName() + "达到...");  
  7.             try {  
  8.                 barrier.await();  
  9.             } catch (Exception e) {  
  10.                 e.printStackTrace();  
  11.             }  
  12.             System.out.println(Thread.currentThread().getName() + "执行完成...");  
  13.         }  
  14.     }  
  15.       
  16.     public static void main(String[] args) {  
  17.         barrier = new CyclicBarrier(5);  
  18.         for(int i = 1 ; i <= 5 ; i++){  
  19.             new threadTest1().start();  
  20.         }  
  21.     }  
  22. }  

——执行结果:

Thread-0达到...
Thread-1达到...
Thread-3达到...
Thread-2达到...
Thread-4达到...
Thread-4执行完成...
Thread-0执行完成...
Thread-1执行完成...
Thread-2执行完成...
Thread-3执行完成...

2、线程等待到一定条件后,执行某项任务。比如说我们等车,只有当车坐满后,汽车才会发动。

这个只需要对上面的代码进行小动作的改动即可:

[java] view plain copy print ?
  1. public class CyclicBarrierTest_2 {  
  2.     private static CyclicBarrier barrier;  
  3.       
  4.     static class threadTest1 extends Thread{  
  5.         public void run() {  
  6.             System.out.println(Thread.currentThread().getName() + "达到...");  
  7.             try {  
  8.                 barrier.await();  
  9.             } catch (Exception e) {  
  10.                 e.printStackTrace();  
  11.             }  
  12.             System.out.println(Thread.currentThread().getName() + "执行完成...");  
  13.         }  
  14.     }  
  15.       
  16.     public static void main(String[] args) {  
  17.         barrier = new CyclicBarrier(5,new Runnable() {  
  18.               
  19.             @Override  
  20.             public void run() {  
  21.                 System.out.println("执行CyclicBarrier中的任务.....");  
  22.             }  
  23.         });  
  24.         for(int i = 1 ; i <= 5 ; i++){  
  25.             new threadTest1().start();  
  26.         }  
  27.     }  
  28. }  

——-执行结果:

Thread-0达到...
Thread-1达到...
Thread-3达到...
Thread-4达到...
Thread-2达到...
执行CyclicBarrier中的任务.....
Thread-2执行完成...
Thread-0执行完成...
Thread-3执行完成...
Thread-1执行完成...
Thread-4执行完成...

 

参考文献:

1、Java多线程系列–“JUC锁”10之 CyclicBarrier原理和示例

你可能感兴趣的:(java,thread,并发,线程)