[JAVA concurrent package source code analysis] Cyclic fence: CyclicBarrier

1. Know the CyclicBarrier

Most people are unfamiliar with CyclicBarrier. In fact, CyclicBarrier is a multi-threaded concurrency control tool, which is very similar to CountDownLatch. It implements count waiting between threads, that is to say, one thread or multiple threads wait for other threads to complete tasks, but more than CountDowwnLatch is complex.

CyclicBarrier means circular fence. The so-called fence is an obstacle that prevents others from entering. In multi-threading, using this tool class is to prevent thread execution, so how does it prevent it? It will be described in detail below. The preceding Cyclic means cycle, which means that the counter can be used cyclically. For example, if there are 5 threads, the tool class will wait for the five threads to reach the specified obstacle point. After the corresponding actions are performed, the counter will be cleared and wait for the arrival of the next batch of threads.

Let's take a look at the internal structure of CyclicBarrier and the dependencies between classes:
write picture description here
The above picture is part of the code inside CyclicBarrier. From the above figure, we can draw the construction diagram of the tool class as follows:
write picture description here

2. Usage scenarios

There are also rich usage scenarios for this tool class, and a simple example is used here to illustrate. For example, the commander of 10 soldiers here gave an order, requiring all 10 soldiers to gather to report first, and then go to perform the task together after the report is completed.

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

For the above CyclicBarrier construction method, it receives two parameters, the first parameter is the total number of counters, the total number of threads participating in the count, and the second parameter barrierAction is a Runnable interface, which is the action to be done after a count is completed.
For the above case, let's demonstrate the scenario with code:

package cn.just.thread.concurrent;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 测试循环栅栏:CycleBarrier(int parties,Runnable barrierAction);
 * 第一个参数表示计数的总数,即参与的线程总数
 * 第二个参数表示当一次计数完成后,系统会执行的动作
 * @author Shinelon
 *
 */
public class CycleBarrierDemo {
    public static class Soldier implements Runnable{
        private String soldier;
        private final CyclicBarrier cyclic;

        public Soldier(String soldier, CyclicBarrier cyclic) {
            super();
            this.soldier = soldier;
            this.cyclic = cyclic;
        }

        @Override
        public void run() {
            try{
                //等待所有士兵到齐
                cyclic.await();
                doWork();
                //等待所有士兵去工作
                cyclic.await();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        private void doWork() {
            try{
                Thread.sleep(Math.abs(new Random().nextInt()%10000));
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier+":任务完成!");
        }
    }

    public static class BarrierRun implements Runnable{
        boolean flag;
        int N;

        public BarrierRun(boolean flag, int n) {
            super();
            this.flag = flag;
            N = n;
        }

        @Override
        public void run() {
            if(flag){
                System.out.println("司令:【士兵"+N+"个,任务完成】");
            }else{
                System.out.println("司令:【士兵"+N+"个,集合完毕】");
                flag=true;
            }

        }
    }

    public static void main(String[] args) {
        final int N=10;
        Thread[] allSoldier=new Thread[N];
        boolean flag=false;
        CyclicBarrier cyclic=new CyclicBarrier(N, new BarrierRun(flag, N));
        //设置障碍点,主要是为了执行这个方法
        System.out.println("集合队伍");
        for(int i=0;i<N;++i){
            System.out.println("士兵"+i+"报道!");
            allSoldier[i]=new Thread(new Soldier("士兵"+i, cyclic));
            allSoldier[i].start();
        }
    }
}

Here is the running result:
write picture description here

In the above code, an internal method of the utility class is involved:
await() waits for all thread counts to complete. This method calls the dowait method internally, and uses the reentrant lock to lock in the dowait method. The waiting process of a counter is realized. Let's dive into the source code below.

3. In-depth source code

The dowait method is mentioned above, and the following is the source code of the method:

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //标志着每一个线程,当一个线程到来就生成一个新生代
            final Generation g = generation;
            //当计数器被破坏,抛出BrokenBarrierException异常
            if (g.broken)
                throw new BrokenBarrierException();
            //当线程被中断。抛出中断异常
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            //当一个线程到来时count减1,直到count为0则计数完成
            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();
                      //如果调用超时,调用awaitNanos方法等待
                    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();
                //如果不是同一个线程,则返回index
                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            //释放锁
            lock.unlock();
        }
    }

Explain the above source code, for each thread, it will have a generation flag to distinguish different threads (I understand it this way), because there is a property broken in the generation object to indicate whether the counter is destroyed or counted Whether to complete, the default is false:

 private static class Generation {
        boolean broken = false;
    }

CyclicBarrier sets two exceptions, one is BrokenBarrierException and the other is InterruptedException. I believe everyone is familiar with InterruptedException. If an interruption occurs, an exception is thrown. BrokenBarrierException is thrown when the counter is destroyed. When a thread arrives, count-1, and then judge whether the count is 0. If it is zero, the count is completed, and the following corresponding actions are performed to enter the next loop count:

  final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //更新标志
                    nextGeneration();
  /**
     * Updates state on barrier trip and wakes up everyone.
     * Called only while holding lock.
     */
 private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

According to the above scenario, we can understand that when 10 soldiers perform a task, the count is 10. Each time a soldier arrives, the count is -1. When all 10 soldiers arrive, the count is 0, and then the BarrierRun thread is executed to perform the corresponding action. Then call the nextGeneration method to update the flag and wake up all waiting threads to continue downward.
It calls the breakBarrier() method when judging whether the counter has completed a count:

    /**
     * Sets current barrier generation as broken and wakes up everyone.
     * Called only while holding lock.
     */
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

This method also updates the flag and wakes up all waiting threads.

In the entire for loop that follows, it is determined whether the current thread is interrupted, whether the counter is destroyed, and whether the wait times out.

  1. If the wait times out, call the awaitNanos method to continue to wait. This method is a method of the implementation class of the Contition interface, allowing the thread to wait at the appropriate time or be notified within a specific time, and continue to execute. The internal implementation of this method is complex, and the author's ability It is limited and will not be analyzed here. If you are interested, you can check the source code yourself.
    write picture description here
  2. It will determine whether all threads have arrived, and if all threads have finished executing, the next loop will be executed
//如果所有线程都已经到达或者被中断则计数完成,进入下一次循环
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    }

3. At the same time, it will also judge whether it is the same thread and update the flag.

//如果不是同一个线程,则返回index
                if (g != generation)
                    return index;

It releases the lock when all of the thread's tasks have completed.

So far, this article has introduced the introduction of the CyclicBarrier tool class. My ability is limited. If there is any inadequacy, please advise. Thank you!
Welcome to the WeChat public account:
write picture description here


Guess you like

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