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

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

揭秘之前,我们先来看下对应的API文档:

 

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

 

方法摘要
 int await()
          在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
int await(long timeout, TimeUnit unit)
          在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
 int getNumberWaiting()
          返回当前在屏障处等待的参与者数目。
 int getParties()
          返回要求启动此 barrier 的参与者数目。
 boolean isBroken()
          查询此屏障是否处于损坏状态。
 void reset()
          将屏障重置为其初始状态。

  根据对应的API文档,我们来逐一看下对应的实现原理

 

1await()的实现原理

前面我们看到CountDownLauch是自己实现了一个AQSSync同步器,那CyclicBarrier是不是也这样的呢?我们来看下对应的实现代码:

 

public int await() throws InterruptedException, BrokenBarrierException {

        try {

            return dowait(false, 0L);

        } catch (TimeoutException toe) {

            throw new Error(toe); // cannot happen;

        }

    }

 

是调用内部的一个dowait方法执行的,这里指定了默认的情况不使用超时!

 

private int dowait(boolean timed, long nanos)

        throws InterruptedException, BrokenBarrierException,

               TimeoutException {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            final Generation g = generation;

 

            if (g.broken)

                throw new BrokenBarrierException();

 

            if (Thread.interrupted()) {

                breakBarrier();

                throw new InterruptedException();

            }

 

           int index = --count;

           if (index == 0) {  // tripped

               boolean ranAction = false;

               try {

          final Runnable command = barrierCommand;

                   if (command != null)

                       command.run();

                   ranAction = true;

                   nextGeneration();

                   return 0;

               } finally {

                   if (!ranAction)

                       breakBarrier();

               }

           }

 

            // loop until tripped, broken, interrupted, or timed out

            for (;;) {

                try {

                    if (!timed)

                        trip.await();

                    else if (nanos > 0L)

                        nanos = trip.awaitNanos(nanos);

                } catch (InterruptedException ie) {

                    if (g == generation && ! g.broken) {

                        breakBarrier();

           throw ie;

           } else {

           // We're about to finish waiting even if we had not

           // been interrupted, so this interrupt is deemed to

           // "belong" to subsequent execution.

           Thread.currentThread().interrupt();

           }

                }

 

                if (g.broken)

                    throw new BrokenBarrierException();

 

                if (g != generation)

                    return index;

 

                if (timed && nanos <= 0L) {

                    breakBarrier();

                    throw new TimeoutException();

                }

            }

        } finally {

            lock.unlock();

        }

    }

 

dowait的代码有点长,我们来慢慢分析一下。

步骤一:首先进入这个方法,第一步是锁定,使用了可重入的独占锁,防止并发情况数据的不正确(也就是同时只能有一个await方法在执行,除非已经阻塞或中断)

 

private final ReentrantLock lock = new ReentrantLock();

final ReentrantLock lock = this.lock;

lock.lock();

 

步骤二:然后是取当前线程的Generation,看是否已经被破坏(此处是描述屏障点被破坏的情况),如果已经被破坏则抛出异常BrokenBarrierException,否则正常继续执行。Generation为CyclicBarrier的一个内部类实现,只有一个状态位。

 

if (g.broken)

      throw new BrokenBarrierException();

private static class Generation {

        boolean broken = false;

    }

 

步骤三:然后是判断当前线程是否已经被中断,如果中断则唤醒其他所有线程并告知其他线程屏障已经被破坏(因为一旦一个线程),同时抛出异常InterruptedException并返回。

 

if (Thread.interrupted()) {

     breakBarrier();

     throw new InterruptedException();

}

private void breakBarrier() {

    generation.broken = true;

    count = parties;

    trip.signalAll();

}

 

步骤四:如果上面都运行正常,则当前的任务数减一,如果当前减一后端的任务数为零,则说明到达屏障点,此时如果有需要到达屏障点之后要执行的任务则执行(上面API文档中可以看到,CyclicBarrier构造函数有一个可以传输一个Runable的实例,用于到达屏障点后执行),如果执行失败则唤醒告知其他线程屏障遭到破坏,成功则重新构建一个Generation(此时会唤醒其他线程,因为当前线程是屏障线程,其他线程还处于等待状态)

 

int index = --count;

   if (index == 0) {  // tripped

       boolean ranAction = false;

       try {

          final Runnable command = barrierCommand;

            if (command != null)

                 command.run();

            ranAction = true;

            nextGeneration();

            return 0;

        } finally {

            if (!ranAction)

               breakBarrier();

        }

    }

private void nextGeneration() {

   // signal completion of last generation

   trip.signalAll();

   // set up next generation

   count = parties;

   generation = new Generation();

}

 

步骤五:如果刚才的操作返回的不是零,则说明还没有到达屏障点,则执行自旋于条件等待。

 

// loop until tripped, broken, interrupted, or timed out

for (;;) {

   try {

        if (!timed)

             trip.await();

        else if (nanos > 0L)

             nanos = trip.awaitNanos(nanos);

    } catch (InterruptedException ie) {

        if (g == generation && ! g.broken) {

            breakBarrier();

           throw ie;

    } else {

           // We're about to finish waiting even if we had not

           // been interrupted, so this interrupt is deemed to

           // "belong" to subsequent execution.

           Thread.currentThread().interrupt();

    }

}

 

步骤六:自旋过程中如果发现屏障被破坏或者线程被中断或者超时则抛出异常返回。如果一切正常且都到达了屏障点则返回到达屏障点的索引。

 

if (g.broken)

     throw new BrokenBarrierException();

if (g != generation)

     return index;

if (timed && nanos <= 0L) {

     breakBarrier();

     throw new TimeoutException();

}

 

步骤七:最后一步就是解锁了

 

finally {

     lock.unlock();

}

 

看到这里很多人可能都会有五个疑问:

1、  里面的trip到底是什么东西?

2、  ReentrantLock不是独占锁么?锁定了其他线程还能执行个屁呀?

3、  阻塞是需要进行park操作的,为啥这个没有呢?

4、  Generation只是一个标志位,为嘛还要单独整一个内部类呢?

5、  发生异常时只是更改了一个generation的标志位,为嘛任务完成时需要新建一个对象,而不是直接修改标志位呢?

我们来逐一看下这几个问题

前三个问题:trip到底是什么东西呢?独占锁问题如何解决?阻塞如何解决?我们来看一个代码

 

/** Condition to wait on until tripped */

private final Condition trip = lock.newCondition();

 

原来trip仅仅只是一个ReentrantLockCondition,中间是Conditionawait导致了锁的释放和线程的阻塞,一下子解释了一二三 三个问题,具体可参考签名提到的Condition的原理分析。

第四个问题:其实这个是Java传递数据的问题,我们知道Java在数据传递过程中,对于基本数据类型是值传递,对于引用类型数据是引用传递(String除外),标志位作为一个基本数据类型,需要在不同的方法中变换,必须有一个对象来进行封装,因此才有了这么一个只含标志位的内部类。

第五个问题:我们知道其实generation作为这么多线程共同的屏障破裂标志,任何一个线程发现屏障破裂之后都会抛出异常,同时也有该标志的操作权限(请注意如下代码,线程操作的都是g而不是generation本身,因为g是线程私有的破裂标志引用,而generation是每个批次都公用的),如果每个地方去维护这个标志开销还是比较大的,不如直接新建一个屏障破裂标志。

 

final Generation g = generation;

 

另外线程持有的generation和全局的generation是否是同一个将被视为自旋的出口,如果是同一个说明当前的批次还没有执行完毕(或者执行出错了),则继续自旋阻塞,如果不一致则说明已经建立下一个屏障,当前批次已经正常执行完毕可正常退出返回对应的到达屏障的索引。

我们再来回顾一下步骤五的自旋代码,再来看下为啥一定需要这么一个标志位、此处如果当前线程发生了异常,那么即时唤醒了其他线程,g != generation也不成立(没有执行nextGeneration),此时自旋很快会再次执行tripawait方法,而此时再没有线程去唤醒它,因此必须在此时予以异常退出!

 

public boolean isBroken() {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            return generation.broken;

        } finally {

            lock.unlock();

        }

    }

 

此外,CyclicBarrier还提供了查询当前等待的线程个数和重置屏障的方法。

 

public int getNumberWaiting() {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            return parties - count;

        } finally {

            lock.unlock();

        }

    }

 

重置屏障首先会将当前屏障等待的线程全部中断,其实就是更改屏障已损坏的标志位,然后启动一个新的generation标识,并唤醒所有的线程(线程启动后将异常退出)。

 

public void reset() {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            breakBarrier();   // break the current generation

            nextGeneration(); // start a new generation

        } finally {

            lock.unlock();

        }

    }

 

CyclicBarrier相当于CountDownLauch来说,每次通过重置方法重新恢复一个屏障组,相对于新创建一个实例来说维护的代价小很多,建议如果有重复性操作的还是使用CyclicBarrier好一点!

 

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