Recyclable barrier CyclicBarrier (source code analysis)

The principle of the starting gun     has been analyzed in the previous article . Here is a summary of the difference between CountDownLatch and CyclicBarrier.

    1. The starting gun is disposable and cannot be reset, and the loop barrier can be reused (reset)

    2. The starting gun is used when all tasks are finished and exited uniformly, and the circular barrier is used when the uniform pace is not started before the task.

For example, when calculating the total transaction amount of the bank, we need to use multiple threads to calculate the total amount of each time period. If we want to know the total amount of the bank for one day, we have to use the starting gun to ensure all the time. The calculation of the total amount is performed after the total amount in the segment has been calculated. If we want the threads of computation to compute in parallel at the same time, we have to use barriers. Here's a simple example, I didn't use the function of the loop:

  ExecutorService executor= Executors.newFixedThreadPool(5);
        final CyclicBarrier cyclicBarrier=new CyclicBarrier(5,new Runnable(){
            public void run() {
                System.out.println("All athletes are ready");

            }
        });
        for (int i = 1; i < 6; i++) {
            executor.execute(new Runner(i,cyclicBarrier));
        }
        cyclicBarrier.reset();

The output is:

Athlete 1 is ready
Current time: 2018-05-04 14:11:15
Athlete 2 is ready
Current time: 2018-05-04 14:11:16
Athlete 3 is ready
Current time: 2018-05-04 14:11:17
Athlete 4 is ready
Current time: 2018-05-04 14:11:18
Athlete 5 is ready
Current time: 2018-05-04 14:11:19
All athletes are ready
Athlete No. 5 starting time: 2018-05-04 14:11:19
Athlete No. 5 is running
Athlete No. 4 starting time: 2018-05-04 14:11:19
Athlete No. 4 is running
Athlete No. 3 starting time: 2018-05-04 14:11:19
Athlete 3 is running
Athlete No. 1 starting time: 2018-05-04 14:11:19
Athlete No. 1 is running
Start time of No. 2 athlete: 2018-05-04 14:11:19
Athlete 2 is running

The code of the Runner is as follows:

static class Runner extends  Thread{
        int Number;
        CyclicBarrier cyclicBarrier;
        public Runner(int Number,CyclicBarrier cyclicBarrier){
            this.Number = Number;
            this.cyclicBarrier = cyclicBarrier;
        }
        public void run() {
            try {
                Thread.sleep(Number*1000); //To make the result clearer
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            System.out.println(Number+"Number player is ready");
            System.out.println("Current time: "+sdf.format(new Date()));
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace ();
            } catch (BrokenBarrierException e) {
                e.printStackTrace ();
            }
            doRun (Number);
        }
        private void doRun(int number) {
            System.out.println(Number+" start time of the athlete: "+sdf.format(new Date()));
            System.out.println(Number+"Number athlete is running");
        }
    }

It can be clearly observed from the code that the first 4 athletes are stopped at cyclicBarrier.await(); and once the 5 athletes are ready, the running competition can start. Therefore, if you master its structure and the reason why await returns, you can basically understand its implementation principle.

When initializing the construction, we passed in a thread object, the parties are 5. CyclicBarrier has a Generation and holds a RentrantLock and a Condition, then its implementation principle can be guessed, that is, it will be unsatisfied by judging the conditions. The thread in the condition is added to the waiting queue of the Condition, and then after the condition is satisfied, use signalAll to wake up all the threads in the waiting queue.

private static class Generation {
        boolean broken = false;
    }

    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();

Regardless of what Generation is used for, let's go to the await method to find out:

return dowait(false, 0L);  

It appears that the dowait method actually implemented:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();//lock
        try {
            final Generation g = generation; //There is a ** object, ignore it first

            if (g.broken)
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {
                breakBarrier();//Response to interrupt
                throw new InterruptedException();
            }

            int index = --count; //count is assigned by the incoming parties during construction
            if (index == 0) { // condition is met
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();//This is the condition to implement the first execution of the thread we passed in after the condition is met
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction) //If it is still true here, it means that the above code was not executed successfully
                        breakBarrier();
                }
            }

            
            for (;;) {//The condition is not met
                try {
                    if (!timed)   
                        trip.await();//Because await has no parameters, our program will execute to this -----core
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);//nanos=0, if it is not 0, the thread will wait for a while and return by itself
                } 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();
        }
    }

It seems that the conjecture is completely correct. Next, the code for nextGeneration is posted:

  private void nextGeneration() {

        trip.signalAll();  //core

        count = parties;
        generation = new Generation(); //This is what can be cycled, reset the state after each wake-up is complete
    }

 When breakBarrier() is used to respond to interrupts, if an external interrupt is encountered when await is executed somewhere, this method will wake up all threads waiting in the queue. Finally, let's talk about the reset function.
public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

In fact, it is understood that the current state is not released artificially, and then reset back to the previous state.

On the other hand, both the starter gun and the loopable barrier can achieve the same function, such as walking together at a unified pace. For the starter gun, you can use await to block at the beginning of the thread task, and then release all threads in the main thread countdown. For recyclable barriers, as long as await is placed, it is ok.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325763179&siteId=291194637