Take a look at Java's locks - CountDownLatch and CyclicBarrier

Keywords: Java Redis REST Spring

Take a look at the locks CountDownLatch and CyclicBarrier in Java

  • Preface
  • Basic introduction
  • Use and difference
  • Core source code analysis
  • summary

Preface

Several articles have been written in Java JUC package. First of all, I spent 5 articles on AQS from the perspective of source code analysis. Why do I spend 5 articles on AQS? It's because AQS is really important. Many implementations provided in JUC are based on AQS, so it's very important to understand the code in AQS Read more times and search for articles online. There are many ways and materials to learn in this time. As long as you are willing to spend time, I believe you will know more than others!

OK, there are a lot of nonsense today. Let's get to the main point. I also continued to write the third article of java lock CountDownLatch, but I found that CyclicBarrier is similar to it in function, but in fact, they are different implementations, and many people on the Internet doubt the usage scenarios and differences of the two. Let's write it together by the way today

Basic introduction

CountDownLatch

CountDownLatch means locking and countdown locking. Both CountDownLatch and semaphore are the realization of thread sharing mode in AQS, which allows multiple threads to have one resource at the same time. Semaphore is to control the concurrent number of threads. In other words, it is to control the number of threads occupying resources. CountDownLatch is to create locked threads Wait for multiple threads that occupy resources to execute before executing their own methods. The explanation may be a little obscure. I will describe it in demo below

CyclicBarrier

CyclicBarrier is a barrier lock compiled in Chinese, and it can be recycled. I'll explain it in the source code later. It means that multiple threads can wait under one condition, which is equivalent to a barrier. Only when all threads arrive, can you continue to do other things. If you don't understand it for the moment, keep looking down. I'm sure you can understand it later!

Use and difference

Use of CountDownLatch

After all, let's use a Demo to see the specific usage:

/**
 * @ClassName CountDownLatchDemo
 * @Auther burgxun
 * @Description: Countdown lock demo
 * @Date 2020/4/11 12:51
 **/
public class CountDownLatchDemo {
    public static void main(String[] args) {
        long timeNow = System.currentTimeMillis();
        CountDownLatch countDownLatch = new CountDownLatch(3);

        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 3, 10, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(5));
        poolExecutor.prestartCoreThread();
        try {
            PrintLog("Perform health check of seckill system");
            poolExecutor.execute(new CheckMQ(countDownLatch));
            poolExecutor.execute(new CheckRPCInterface(countDownLatch));
            poolExecutor.execute(new PreRedisData(countDownLatch));
            countDownLatch.await();
            PrintLog("After the health examination is completed, the total cost is:" + (System.currentTimeMillis() - timeNow));

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            poolExecutor.shutdown();
        }
    }

    public static void PrintLog(String logContent) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm.ss.SSS");
        System.out.println(String.format("%s : %s", simpleDateFormat.format(new Date()), logContent));
    }

    static class CheckRPCInterface implements Runnable {

        private CountDownLatch countDownLatch;

        public CheckRPCInterface(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                PrintLog("RPC Interface detection starts");
                Thread.sleep(1000);
                PrintLog("RPC Interface test completed");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }
    }

    static class PreRedisData implements Runnable {

        private CountDownLatch countDownLatch;

        public PreRedisData(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                PrintLog("Redis Data start preheating start execution");
                Thread.sleep(3000);
                PrintLog("Redis Data start preheating completed");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }
    }

    static class CheckMQ implements Runnable {

        private CountDownLatch countDownLatch;

        public CheckMQ(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                PrintLog("MQ Test start");
                Thread.sleep(2000);
                PrintLog("MQ Detection completed");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }
    }

}

Execution result:

org.example.CountDownLatchDemo
2020-04-11 01:23.33.859 : Perform health check of seckill system
2020-04-11 01:23.33.868 : MQ Test start
2020-04-11 01:23.33.870 : RPC Interface detection starts
2020-04-11 01:23.33.870 : Redis Data start preheating start execution
2020-04-11 01:23.34.870 : RPC Interface test completed
2020-04-11 01:23.35.870 : MQ Detection completed
2020-04-11 01:23.36.871 : Redis Data start preheating completed
2020-04-11 01:23.36.871 : Health examination completed, total cost: 3065

What's the above scenario? Let me describe it. I believe that many small partners know more or less about seckill system. For example, we need to go online at 12 o'clock for a seckill system. Before going online, we must make preparations for some work, such as checking some core interfaces, preheating Redis core data, checking MQ message queues, etc. if all these work is completed, We might just open our second kill portal. The above Demo describes such a thing. Of course, some monitoring needs to be done in the production environment. From the above Demo, we can see that when the current thread creating CountDownLatch is executed to countDownLatch.await, the thread is blocked. Only when the remaining detection threads are executed, the current thread will be awakened to execute the subsequent logic,

Note that the blocking thread here is the thread that creates countDownLatch. The rest of the threads will perform the countDownLatch.countDown operation only after the execution is completed! Understanding this is important to understand the difference between understanding and CyclicBarrier

The use of CyclicBarrier

Now I'm looking at the Demo of CyclicBarrier

/**
 * @ClassName CyclicBarrierDemo
 * @Auther burgxun
 * @Description: Barrier lock. The whole family went out to play, Demo
 * @Date 2020/4/11 14:08
 **/
public class CyclicBarrierDemo {
    private static int NUMBER = 3;

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            PrintLog("All right, the family can go out for a tour");
        });
        PrintLog("The whole family is going out for a spring outing");
        createThread(cyclicBarrier, "Dad", 2000).start();
        createThread(cyclicBarrier, "Mom", 5000).start();
        createThread(cyclicBarrier, "Son", 3000).start();
        System.out.println("gogogogo!");
    }

    public static Thread createThread(CyclicBarrier cyclicBarrier, String name, int runTime) {
        Thread thread = new Thread(() -> {
            try {
                PrintLog(String.format("%s Get ready to go out", name));
                Thread.sleep(runTime);
                PrintLog(String.format("%s Pick up the flowers.%s Millisecond", name, runTime));
                if (cyclicBarrier.getNumberWaiting() < (NUMBER - 1)) {
                    PrintLog(String.format("%s Start waiting....", name));
                }
                long time = System.currentTimeMillis();
                cyclicBarrier.await();
                PrintLog(String.format("%s I started to put on my shoes. I waited%s Seconds.", name,
                        (System.currentTimeMillis() - time)));

            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        return thread;
    }


    public static void PrintLog(String logContent) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm.ss.SSS");
        System.out.println(String.format("%s : %s", simpleDateFormat.format(new Date()), logContent));
    }
}

Execution result:

2020-04-11 02:33.35.306 : The whole family is going out for a spring outing
gogogogo!
2020-04-11 02:33.35.313 : Mom starts to pack up and get ready to go out
2020-04-11 02:33.35.313 : My son began to pack up and get ready to go out
2020-04-11 02:33.35.313 : Dad started to pack up and get ready to go out
2020-04-11 02:33.37.314 : It took dad 2000 milliseconds to clean up
2020-04-11 02:33.37.314 : Dad began to wait....
2020-04-11 02:33.38.314 : It took my son 3000 milliseconds to clean up
2020-04-11 02:33.38.314 : The son began to wait....
2020-04-11 02:33.40.313 : Mom took 5000 milliseconds to clean up
2020-04-11 02:33.40.313 : All right, the family can go out for a tour
2020-04-11 02:33.40.314 : Dad started putting on his shoes and I waited 2999 seconds
2020-04-11 02:33.40.314 : Mom started to put on shoes and set off. I've been waiting for 0 seconds
2020-04-11 02:33.40.314 : My son started to put on his shoes. I've been waiting for 2000 seconds

What's the demo above? It's a demo for the whole family to go out, Since you want to go out to play, you must be prepared. Mom needs to make up and change clothes. My son may choose a toy he loves. Dad doesn't think it's OK. Just change clothes. The rest will wait for my son and mom. Only when the whole family is ready, can everyone wear shoes. Then walk to the parking lot and drive away!
It should be noted that the thread creating the CyclicBarrier is non blocking. From the above log of executing gogogo, it can be seen that the blocked thread is the thread executing cyclicBarrier.await, which is the task thread. This is to wait. As long as all threads have reached a point of execution, they can continue to execute the code behind await

If you still don't understand the Demo above, I can say that you must know that for example, a product must be sold to three people in order to enjoy the discount successfully. When the first person buys, he / she orders and waits, but can't pay in a group. The second person is also, until the third person arrives, he / she will be notified to the first person and the second person You can enjoy the preferential price after the order is completed. Now you can pay! Of course, when the fourth person comes, you still have to wait.. This is the best understanding of CyclicBarrier!!!

Difference between the two

From my bold text above, we can see the difference between the two. The core is that the blocked threads are different,

Countdownlatch is the thread that creates the countdownlatch object to call wait and it will block the current thread. Other threads that perform tasks will not block. Only after the execution is completed, modify the counter value in countdownlatch. When the last thread is completed, the counter value changes to 0, and the blocked creation thread will wake up

Cyclicbarrier is the thread that creates the cyclicbarrier object and will not block or call await, Only the thread executing the task will invoke the await method of CyclicBarrier and block the current thread after calling. When the count attribute value in CyclicBarrier is 0, then it is stated that some threads are ready to go through this barrier, wake up the thread calling await before continuing the remaining logic.

I'm going to give you an example of life. I'm sure you've participated in the group building and running of the company. Generally, people are divided into multiple groups. Then each group has a team leader. Take the group building of our company for example. Every year, we go to practice and say that running is running. Running is more than ten kilometers. For the fun of the activity, the company usually sets multiple punch cards in the middle Generally, the team leader will wait for all the team members to arrive at the checkpoint to take a picture and do a task. The team leader's roll call is equivalent to a CountDownLatch. Why do you say that? Because the team leader's roll call is a series of tasks. At this time, he arrives at the checkpoint We have to wait for all the team members to arrive. At this time, the rest of the team members are equivalent to carrying out a cyclical barrier task. Because when the team members arrive at the punch point, they can't continue to walk down. They have to wait for the rest of the team to arrive to continue the next task!

After that, do you understand that CountDownLatch is the team leader's own block to wait for everyone to go together, and cyclicBarrier is the team member who has to wait for other team members to arrive at the punch point to continue the next task!

To be more straightforward, the captain is that the driver has to wait for all the people to start. The team members are the same as the passengers. When they arrive in the bus, they have to wait for all the people to arrive before leaving

Core code analysis

CountDownLatch

First look at the class structure of CountDownLatch. The structure of CountDownLatch is very simple. There is an internal class. Sync continues the AQS class, and the rest is an initialization method and two await methods

Sync

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        /**
         * Set the State value in AQS synchronizer
         */
        Sync(int count) {
            setState(count);
        }

        /**
         * Get the value of State in AQS
         */
        int getCount() {
            return getState();
        }

        /**
         * Overriding the AQS method, trying to get the resource if the current status value is equal to 0, then return 1 otherwise return - 1
         * Why is this implemented here? Because this lock is a countdown lock. If the current State is not equal to 0 and the return value is - 1, the current thread needs to be blocked
         * The countDown method is called only when the State is equal to 0, which means that all threads taking up resources have finished execution
         * At that time, the thread does not need to block the executable, and it will be
         */
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        /**
         * Release resources in shared mode
         */
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (; ; ) {//spin
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;//Synchronizer state value State -1 every time it is released
                if (compareAndSetState(c, nextc))//Why are CAS operations practical? Here and Semaphore semaphores are due to the problem of multithreading
                    return nextc == 0;
            }
        }
    }

I have annotated all the above codes. I believe I can understand them. The method is very simple, mainly two methods that rewrite AQS's tryAcquireShared and tryreleased shared

Internal method

 /**
     * default constructor 
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * The method of the resource acquired by AQS in shared mode responds to the version of the interrupt
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * Access to resources with deadline in sharing mode
     */
    public boolean await(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * Release synchronizer State resource minus 1
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * Returns the current synchronizer status value
     */
    public long getCount() {
        return sync.getCount();
    }

The above is CountDownLatch's method, which is not easy to talk about. It calls the method in AQS to realize. This has been explained a lot. If you don't talk about it, don't talk about it. I have a clear idea of the above~

CyclicBarrier

The class structure of CyclicBarrier is totally different from CountDownLatch. Instead of directly inheriting the AQS class, it uses the combination of ReentrantLock and Condition to realize the function.

Constructor

First look at the two constructors:

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;//Indicates the number of threads needed to open the Barrier
        this.count = parties;// Currently, there are still threads that need to open the barrier
        this.barrierCommand = barrierAction;//Callback function after opening the barrier
    }

    /**
     * Creating a cyclobarrier parties indicates the number of threads needed to open the Barrier
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

After reading the constructor, look at the following methods

reset method

 /**
     * break Current barrier and open next barrier
     */
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // Break the current barrier
            nextGeneration(); // Open a new barrier
        } finally {
            lock.unlock();
        }
    }
    
     private void breakBarrier() {
        generation.broken = true;//Set the broken value of the current barrier,
        count = parties;//Reset the count value the barrier has broken the count value. You need to prepare for the next barrier
        trip.signalAll();//Wake up all threads previously waiting on this barrier
    }
    
     private void nextGeneration() {
        trip.signalAll();//All threads waiting on the barrier before waking up
        count = parties;//Number of barriers to be opened
        generation = new Generation();//Reset the default value of broken in generation, false
    }

breakBarrier and nextGeneration do almost the same thing. breakBarrier mainly deals with the current barrier. nextGeneration does something to open a new barrier, which is why it is called CyclicBarrier circular barrier, because it can be opened and closed and then opened again

await method

There are two pulic await methods in the CyclicBarrier, one is waiting without time, the other is waiting with time, and the core is the dowait method of modulation

 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));
    }
   //Core method dowait
   private int dowait(boolean timed, long nanos)
            throws InterruptedException, BrokenBarrierException,
            TimeoutException {
        final ReentrantLock lock = this.lock;//Reentry lock ensures thread safety
        lock.lock();
        try {
            final Generation g = generation;

            if (g.broken)//If the current Barrier has broken, a broken Barrier exception will be thrown
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {//If the current thread is interrupted, the breakBarrier is executed and an interrupt exception is thrown
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;// Reduce the count value before each execution
            if (index == 0) {  // It indicates that the conditions for opening the Barrier have been reached
                boolean ranAction = false;//Whether to open the current barrier successfully
                try {
                    final Runnable command = barrierCommand;//Runnable method to enable the execution of Barrier
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();//Reset conditions for next Barrier
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();//If the open Barrier condition has been reached but the execution is not successful, the Barrier will be destroyed
                }
            }

            /**
             * loop until
             * tripped, Wake up by the last thread to Barrier
             * broken, The current Barrier has been destroyed
             * interrupted,Blocked waiting thread interrupted including current thread
             * or timed out Or one of the waiting threads timed out
             * */
            for (; ; ) {//This is a spin
                try {
                    if (!timed)//Whether there is a waiting time. If there is no waiting time, it will directly block the current thread from entering the tripConditionQueue
                        trip.await();
                    else if (nanos > 0L)//If there is a waiting time and the waiting time is greater than 0
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {//Capture interrupts during thread wait
                    if (g == generation && !g.broken) {// If the generation does not change, it means that it is still in the previous Barrier and the Barrier is not broken
                        breakBarrier();//Then perform the operation of destroying Barrier to wake up the waiting thread
                        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();//Reset interrupt ID
                    }
                }

                if (g.broken)//After wake-up, it is found that the Barrier has been broken and a BrokenBarrierException exception is thrown
                    throw new BrokenBarrierException();

                /**
                 *After wake-up, it is found that the waiting Barrier is inconsistent with the wake-up Barrier. The Barrier has replaced the previous index
                 * Because a thread can open multiple barriers, such as reset, which will wake up blocked threads. At this time, the current generation object is new
                 * It's obviously not an object
                 */
                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {//If the waiting time is up, break the barrier and return to TimeoutException
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

The core of CyclicBarrier's method is here. If you are interested, you can try to understand it by comparing with the source code

summary

CyclicBarrier and CountDownLatch All of them can do yes. Multiple threads wait and then start to do the next step, but the implementation subjects are different. Through the above code and Demo analysis, the implementation subjects of CountDownLatch and the next step operations are the creator's own threads and cannot be reused. The implementation subjects of CyclicBarrier are other threads and can be reused

Posted by jokerofsouls on Sat, 11 Apr 2020 04:48:54 -0700