Deep understanding of CyclicBarrier

introduction

Starting from jdk1.5, a class java.util.concurrent.CyclicBarrier was introduced to control multiple threads to wait for each other. These threads will continue to execute only when multiple threads arrive. Unlike CountDownLatch , CyclicBarrier instances can be reused, also known as repeated fences

use


class CyclicBarrierAgainDemo implements Runnable{
    
    
   @Override
   public void run() {
    
    
       System.out.print("------------");
   }
	public static void main(String[] args) {
    
    
	     int totalThread = 10;
	     CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread,new CyclicBarrierAgainDemo ());
	     ExecutorService executorService = Executors.newCachedThreadPool();
	     for (int i = 0; i < totalThread; i++) {
    
    
	         executorService.execute(() -> {
    
    
	             System.out.print("before..");
	             try {
    
    
	                 cyclicBarrier.await();
	             } catch (InterruptedException | BrokenBarrierException e) {
    
    
	                 e.printStackTrace();
	             }
	         });
	     }
	     executorService.shutdown();
	 }
}

Threads that use CyclicBarrier to realize waiting are called participants. Participants only need to executecyclicBarrier.await()You can wait. Since CyclicBarrier maintains a display lock internally, it is possible to know who of the participants is the last to executecyclicBarrier.await(). When the last thread is executed, other parties using the corresponding CyclicBarrier instance will be awakened, and the last thread itself will not be suspended. The flow chart is as follows:
Insert picture description here

CyclicBarrier source code analysis

Note: I will comment out the function of each method in the source code, you can refer to the comments for understanding.

First look at the construction method of the CyclicBarrier class:


public CyclicBarrier(int parties) {
    
    
        this(parties, null);
    }
    
public CyclicBarrier(int parties, Runnable barrierAction) {
    
    
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;//parties 指示计数器的初始值
        this.count = parties;
        this.barrierCommand = barrierAction;//所以线程到达屏障后会执行一次
    }

Among them, the second constructor method is the more commonly used one.

await()

public int await() throws InterruptedException, BrokenBarrierException {
    
    
        try {
    
    
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
    
    
            throw new Error(toe); // cannot happen
        }
    }
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();
            }
			//计数器减1
            int index = --count;
            //最后一个线程到达栅栏
            if (index == 0) {
    
      // tripped
                boolean ranAction = false;
                try {
    
    
                	//栅栏被打破市执行的方法
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //开启下一代,again
                    nextGeneration();
                    return 0;
                } finally {
    
    
                	//确保其他线程都够正常运行
                    if (!ranAction)
                        breakBarrier();
                }
            }
            //自旋
            for (;;) {
    
    
                try {
    
    
                	//判断时候有等待时间限制
                    if (!timed)
                    	//还是使用的Condition
                        trip.await();
                    else if (nanos > 0L)
                    	//如果有,设置等待时间,时间到后,自动唤醒
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
    
    
                	//当前 线程被中断,并且栅栏未被打破,则唤醒之前等待的线程
                    if (g == generation && ! g.broken) {
    
    
                        breakBarrier();
                        throw ie;
                    } else {
    
    
                        //若在捕获中断异常前已经完成在栅栏上的等待, 则直接调用中断操作
                        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. Obtain the display lock, determine whether the current thread state is interrupted, if so, executebreakBarrier Method to wake up all threads that were blocked before and reset the counter
  2. The counter count is reduced by 1. If count==0, it means that the last thread reaches the fence, and then executes the previously specified Runnable interface, and executes it at the same time nextGenerationApproach to the next generation
  3. Otherwise, enter the spin and determine whether the current thread enters timed waiting or non-timed waiting. If it is interrupted during the waiting process, executebreakBarrier Method to wake up all threads that were blocked before
  4. Determine if it is due to executionbreakBarrierMethod is awakened, if yes, throw an exception
  5. Determine whether it is a normal replacement operation and awakened, if so, return the value of the counter
  6. Determine whether it is awakened by timeout, if it is, wake up all threads that were blocked before and throw an exception
  7. Releasing the lock
    We can see that the blocking method used here uses the await()/awaitNanos(long time) method of the condition variable Condition . We have carefully analyzed this method before when we put the Condition . We will not analyze it here. Interested students can go to the previous article link: Deep understanding of the condition variable Condition

breakBarrier()

private void breakBarrier() {
    
    
        generation.broken = true;//栅栏被打破
        count = parties;//重置count
        trip.signalAll();//唤醒之前阻塞的线程
    }

nextGeneration()

private void nextGeneration() {
    
    
        //唤醒所以的线程
        trip.signalAll();
        //重置计数器
        count = parties;
        //重新开始
        generation = new Generation();
    }

reset()

Next look at the method of fence reset

public void reset() {
    
    
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
    
    
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
    
    
            lock.unlock();
        }
    }

The difference between CyclicBarrier and CountDownLatch

  1. CountDownLatch can only intercept one round, while CyclicBarrier can achieve cyclic interception.
  2. The counter of CyclicBarrier is controlled by itself, while the counter of CountDownLatch is passed by the usercountDown() Method to control

to sum up

If the code is rightcyclicBarrier.await()The call is not placed in a loop, and the purpose of using CyclicBarrier is not to test high concurrent operations, then the use of CyclicBarrier at this time may be an abuse

Guess you like

Origin blog.csdn.net/xzw12138/article/details/106588912