Source CyclicBarrier explore (JDK 1.8)

CyclicBarrierAlso called loop fence, can be achieved to allow a set of threads running at the fence and blocking function until all threads have reached the fence and then perform together. "Loop" means that CyclicBarriercan be used repeatedly, compared to CountDownLatchonly be used once, CyclicBarrieryou can save a lot of resources, and can also pass task in the constructor, to perform this task when the fence conditions are met. CyclicBarrierIs used ReentrantLock, the primary method will be locked at the time of execution, therefore concurrent performance is not very high.

1. related fields

    //重入锁,CyclicBarrier内部通过重入锁实现线程安全
    private final ReentrantLock lock = new ReentrantLock();
    //线程阻塞时的等待条件
    private final Condition trip = lock.newCondition();
    //需要等待的线程数
    private final int parties;
    //栅栏打开之后首先执行的任务
    private final Runnable barrierCommand;
    //记录当前的分代标记
    private Generation generation = new Generation();
    //当前还需要等待多少个线程运行到栅栏位置
    private int count;

Note that the generationfield used to mark the current fence in the generation. When certain conditions are satisfied (e.g., called resetmethods, etc. or a fence is opened), the state will switch to the next fence, it is actually newa new Generationobject, which is CyclicBarrieran internal class, code that is very simple, as follows:

    private static class Generation {
        boolean broken = false;   //标记栅栏是否被破坏
    }

In actual use, we will use generationthe fields determine whether the current in the same generational, using brokerfield determines whether the barrier is destroyed.

2. Constructor

CyclicBarrierThere are two overloaded constructor, constructor simply related to the above fields are initialized as follows:

    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

3. The core method

  • await
    awaitIs the most commonly used method of developing the same CountDownLatch, like CyclicBarrieralso provides two awaitmethods, one with no parameters, with a timeout parameter, simply call the inside a little dowaitmethod:
    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

    public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

Then take a look critical dowaitmethod:

    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        //加重入锁
        lock.lock();
        try {
            //首先获取年龄代信息
            final Generation g = generation;
            //如果栅栏状态被破坏,抛出异常,例如先启动的线程调用了breakBarrier方法,后启动的线程就能够看到g.broker=true
            if (g.broken)
                throw new BrokenBarrierException();
            //检测线程的中断状态,如果线程设置了中断状态,则通过breakBarrier设置栅栏为已破坏状态,并唤醒其他线程
            //如果这里能够检测到中断状态,那只可能是在await方法外部设置的
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            //每调用一次await,就将需要等待的线程数减1
            int index = --count;
            //index=0表示这是最后一个到达的线程,由该线程执行下面的逻辑
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    //如果在构造器中传入了第二个任务参数,就在放开栅栏前先执行这个任务
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //正常结束,需要唤醒阻塞的线程,并换代
                    nextGeneration();
                    return 0;
                } finally {
                    //try代码块如果正常执行,ranAction就一定等于true,而try代码块唯一可能发生异常的地方就是command.run(),
                    //因此这里为了保证在任务执行失败时,将栅栏标记为已破坏,唤醒阻塞线程
                    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) {
                    //如果线程等待的过程中被中断,会执行到这里
                    //g == generation表示当前还在同一个年龄分代中,!g.broker表示当前栅栏状态没有被破坏
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        //上面的条件不满足,说明:1)g!=generation,说明线程执行到这里时已经换代了
                        //2)没有换代,但是栅栏被破坏了
                        //无论哪种情况,都只是简单地设置一下当前线程的中断状态
                        Thread.currentThread().interrupt();
                    }
                }
                //栅栏被破坏,抛出异常
                //注意,在breakBarrier方法中会唤醒所有等待条件的线程,这些线程会执行到这里,判断栅栏已经被破坏,都会抛出异常
                if (g.broken)
                    throw new BrokenBarrierException();
                //距离上一次设置g变量的值已经过去很长时间了,在执行过程中generation可能已经发生改变,
                //当前线程还是前几代的,不需要再循环阻塞了,直接返回上一代剩余需要等待的线程数
                //注意:代码中breakBarrier方法和nextGeneration方法都会唤醒阻塞的线程,但是breakBarrier在上一个判断就被拦截了,
                //因此走到这里的有三种情况:
                //a)最后一个线程正常执行,栅栏打开导致其他线程被唤醒;不属于当前代的线程直接返回,
                //属于当前代的则可能因为没到栅栏开放条件要继续循环阻塞
                //b)栅栏被重置(调用了reset方法),此时g!=negeration,全都直接返回
                //c)线程等待超时了,不属于当前代的返回就可以了,属于当前代的则要设置generation.broken = true
                if (g != generation)
                    return index;
                //如果线程等待超时,标记栅栏为破坏状态并抛出异常,如果还没超时,则自旋后又重新阻塞
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            //别忘了解锁
            lock.unlock();
        }
    }

dowaitThe method logic is: each call awaitto the thread count will be countreduced 1, the last thread will be countreduced to 0time, incidentally, also perform barrierCommandspecified tasks and generationswitch to the next generation, of course, the most important thing is to wake up before the fence at blocked thread. Due to tripthe corresponding Conditionobject has no place will be modified so that trip.signalAll()will awaken all waiting on the condition of the thread, if the thread is waiting for the process, the other thread will generationbe updated to the next generation, there will be awakened thread some also belong prior to that generation situation.
The next will be on dowaitsome of the methods used for a brief introduction.

  • breakBarrier
    dowaitThere are four local method call breakBarrier, it can see from the name, which will be generation.brokenset true, in addition, will restore countthe values and wakes up all the blocked thread:
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        //唤醒所有的阻塞线程
        trip.signalAll();
    }

Looking at the CyclicBarriersource code, generation.brokenunified breakBarrieris set to approach true, but once generation.brokenset truelater, after checking the code will throw an exception to this state, there is no way to re-use the fence (you can manually call resetreset), and the source code in the following situations will call the breakBarriermethod:
1) the current thread is interrupted
2) passed through the constructor task fails
interrupted 3) condition waiting
4) thread waits timeout
5) explicitly call the resetmethod

  • nextGeneration
    private void nextGeneration() {
        // 唤醒所有的阻塞线程
        trip.signalAll();
        // 开启下一代
        count = parties;
        generation = new Generation();
    }
  • reset
    resetThe main method is the end of this generation, and switch to the next 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();
        }
    }

Introduced here, the whole CyclicBarrierhas been introduced almost over, but the internal process is far from simple, because the logic is encapsulated in a large part AbstractQueuedSynchronizer, this class defines how threads are blocked waiting queue, but also how to be woken up, so if you want depth understanding of logical thread to wait, but also need to carefully study AbstractQueuedSynchronizerthe job. This article describes this part of the content will not be behind if you have time will be a special presentation.

Guess you like

Origin www.cnblogs.com/NaLanZiYi-LinEr/p/12484022.html