Take you to look at Java locks (3)-CountDownLatch and CyclicBarrier

Take you through the locks CountDownLatch and CyclicBarrier in Java

  • Foreword
  • basic introduction
  • Use and difference
  • Core source code analysis
  • to sum up

Foreword

The articles in the Java JUC package have been written several times. First of all, I spent 5 articles on AQS from the perspective of source code analysis. Why did I spend 5 time on this, because AQS is really important, provided by JUC Many of the implementations are based on AQS, so it is very important to understand the code in AQS. If you do n’t understand it, read it a few times. Find more articles online. There are really many ways and materials to learn this time. As long as you are willing to spend Time, I believe I will know more than others!

Okay, there is a lot of nonsense today. Let ’s get to the point. Today I continued to write the third part of the lock in CountDownLatch in java, but found that CyclicBarrier is similar in function to it, but in fact the two are different. Realized, and many people on the Internet are confused about the use scenarios and differences of the two, so I will write it together today.

basic introduction

CountDownLatch

The Chinese translation of CountDownLatch is blocking, and the countdown lock means that CountDownLatch and Semaphore are all implementations of the thread sharing mode in AQS, which is to allow multiple threads to have a resource at the same time. Semaphore is to control the number of concurrent threads. Control the number of threads occupying resources. CountDownLatch is to create a blocked thread and wait for multiple threads occupying resources to complete its execution before executing its own method. It may be a bit obscure. I will describe it below through a demo

CyclicBarrier

CyclicBarrier Chinese is compiled as a barrier lock, and it can be recycled. I will explain it in the source code for a while. It means that letting multiple threads wait under a condition is equivalent to a barrier. Only when all threads have arrived can they continue. Do something else that you don't understand for now, continue to look down, and I believe you can understand later!

Use and difference

Use of CountDownLatch

Having said so much, let's go through a Demo and see the specific use:

/**
 * @ClassName CountDownLatchDemo
 * @Auther burgxun
 * @Description: 倒计时锁CountDownLatch 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("执行秒杀系统的健康检查");
            poolExecutor.execute(new CheckMQ(countDownLatch));
            poolExecutor.execute(new CheckRPCInterface(countDownLatch));
            poolExecutor.execute(new PreRedisData(countDownLatch));
            countDownLatch.await();
            PrintLog("健康检查执行完毕,共计花费:" + (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接口检测开始执行");
                Thread.sleep(1000);
                PrintLog("RPC接口检测完成");

            } 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数据开始预热开始执行");
                Thread.sleep(3000);
                PrintLog("Redis数据开始预热完成");

            } 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检测开始执行");
                Thread.sleep(2000);
                PrintLog("MQ检测完成");

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

}

Results of the:

org.example.CountDownLatchDemo
2020-04-11 01:23.33.859 : 执行秒杀系统的健康检查
2020-04-11 01:23.33.868 : MQ检测开始执行
2020-04-11 01:23.33.870 : RPC接口检测开始执行
2020-04-11 01:23.33.870 : Redis数据开始预热开始执行
2020-04-11 01:23.34.870 : RPC接口检测完成
2020-04-11 01:23.35.870 : MQ检测完成
2020-04-11 01:23.36.871 : Redis数据开始预热完成
2020-04-11 01:23.36.871 : 健康检查执行完毕,共计花费:3065

What is the above scenario? Let me describe it. I believe that many small partners know more or less. For example, we need a spike system to go online at 12 o'clock. We will definitely prepare for work before going online, such as some core interfaces. Inspection, warm-up of Redis core data, detection of MQ message queue, etc. If these tasks are all executed, we may only open our spike entry. The Demo above describes such a thing. Of course, some monitoring needs to be done in the production environment. From the above Demo, we can see that the current thread that creates CountDownLatch. When executing to countDownLatch.await, the thread is To block, only when the rest of the detection thread is completed, the current thread will be woken up to execute the logic behind,

Here everyone pays attention, the blocking thread here is the thread that creates the countDownLatch, and the rest of the threads are only after the execution is completed, the countDownLatch.countDown operation! Understanding this is important for understanding the difference between CyclicBarrier

Use of CyclicBarrier

Below I am looking at the demo of CyclicBarrier

/**
 * @ClassName CyclicBarrierDemo
 * @Auther burgxun
 * @Description: 屏障锁  全家出去玩的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("好的,都收拾好了 全家可以出门旅游了");
        });
        PrintLog("全家准备出门春游");
        createThread(cyclicBarrier, "爸爸", 2000).start();
        createThread(cyclicBarrier, "妈妈", 5000).start();
        createThread(cyclicBarrier, "儿子", 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 开始收拾准备出门", name));
                Thread.sleep(runTime);
                PrintLog(String.format("%s 收拾花了%s 毫秒", name, runTime));
                if (cyclicBarrier.getNumberWaiting() < (NUMBER - 1)) {
                    PrintLog(String.format("%s 开始等待。。。。", name));
                }
                long time = System.currentTimeMillis();
                cyclicBarrier.await();
                PrintLog(String.format("%s 开始穿鞋出发,我等待了%s 秒了", 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));
    }
}

Results of the:

2020-04-11 02:33.35.306 : 全家准备出门春游
gogogogo!
2020-04-11 02:33.35.313 : 妈妈 开始收拾准备出门
2020-04-11 02:33.35.313 : 儿子 开始收拾准备出门
2020-04-11 02:33.35.313 : 爸爸 开始收拾准备出门
2020-04-11 02:33.37.314 : 爸爸 收拾花了2000 毫秒
2020-04-11 02:33.37.314 : 爸爸 开始等待。。。。
2020-04-11 02:33.38.314 : 儿子 收拾花了3000 毫秒
2020-04-11 02:33.38.314 : 儿子 开始等待。。。。
2020-04-11 02:33.40.313 : 妈妈 收拾花了5000 毫秒
2020-04-11 02:33.40.313 : 好的,都收拾好了 全家可以出门旅游了
2020-04-11 02:33.40.314 : 爸爸 开始穿鞋出发,我等待了2999 秒了
2020-04-11 02:33.40.314 : 妈妈 开始穿鞋出发,我等待了0 秒了
2020-04-11 02:33.40.314 : 儿子 开始穿鞋出发,我等待了2000 秒了

What is the above demo? It is a demo that the whole family is going to finish. Since it is going to go out to play, it must be prepared. Mom wants to put on makeup and change clothes. Son may choose a beloved toy with him, dad estimates It's okay, just change your clothes, and the rest will wait for your son and mother. Only when the whole family is ready, can everyone wear shoes, and then walk to the parking lot to drive away!
It should be noted here that the thread that creates the CyclicBarrier is non-blocking. As you can see from the log of the gogogo above, the blocked thread is the thread that executes cyclicBarrier.await, which is the task thread. This is to wait. Wait until all threads have reached a point of execution before they can continue to execute the code behind await

If you still do n’t understand the demo above, I can say that everyone must know a lot of spelling. For example, a product must be filled with 3 people in order to successfully enjoy the discount. The first person to place an order when buying but cannot succeed Group payment, the second is also, the group can not be successful until the third person comes. At this time, the first person will be notified. The second person can successfully enjoy the preferential price when the group is ordered. Now you can pay! Of course, the fourth person still has to wait when he comes. . This is the best understanding of CyclicBarrier! ! !

The difference between the 2

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

CountDownLatch is the thread that creates the countDownLatch object calls await and will block the current thread. The remaining threads that perform tasks will not block, but just modify the counter value in countDownLatch after the execution is completed. When the last thread is completed, the counter value becomes 0. This Will wake up the blocked creation thread

CyclicBarrier is the thread that creates the CyclicBarrier object and will not block or call await. Only the thread that performs the task will call the await method of CyclicBarrier and block the current thread after the call. When the count property in the CyclicBarrier is 0, then Explain that some threads are ready for execution and can cross this barrier, wake up the thread that called await before, and continue to execute the remaining logic.

Then I am giving an example in life. I believe that everyone must have participated in the company ’s team building and running. Generally, people will be divided into multiple groups, and then each group has a captain. Take the company ’s team building as an example. Every year I go to practice to say that running is plain, and one run is more than ten kilometers. For the fun of the activity, the company usually sets up multiple checkpoints to do tasks in the middle. Generally, the team leader will wait for all the players to arrive at the checkpoint. Take a photo to do a task and what will continue to the next level. The name of the captain is equivalent to a CountDownLatch. Why do you say that, because the captain does a series of tasks such as naming, at this time he has to wait for all the punch points When the team member arrives, the remaining team members at this time are equivalent to performing a cyclicBarrier task, because after the team member reaches the punching point, he cannot continue to go down. He must wait for the rest of the team to arrive before continuing to the next task!

At this point, do you understand, simply put, the CountDownLatch is the captain blocking himself and waiting for everyone to go together; cyclicBarrier is the team member who has arrived at the punching point and has to wait for the other team members to arrive before he can continue the next mission!

To be more straightforward, the captain is the driver who has to wait for the passengers to start. The team members are equivalent to passengers. When they arrive in the bus, they have to wait for everyone to arrive before they can go.

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. The rest is an initialization method and two await methods.

Sync

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

        /**
         * 设置AQS同步器里面State的值
         */
        Sync(int count) {
            setState(count);
        }

        /**
         * 获取AQS里面State的值
         */
        int getCount() {
            return getState();
        }

        /**
         * 重写了AQS的方法,尝试获取资源  如果当前的状态值等于0了 那就返回1 否则返回-1
         * 为什么这边要这么实现呢  因为这个锁是一个倒计时锁  如果当前State不等于0 返回值是-1 说明当前线程还是需要阻塞的
         * 只有当State 等于0了 说明占用资源的线程都执行结束了 执行结束会调用countDown方法
         * 那这个时候线程就不需要阻塞了 可执行下去 而且会是
         */
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        /**
         * 共享模式下的释放资源
         */
        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (; ; ) {//自旋
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;//每次释放的时候 同步器状态值State -1
                if (compareAndSetState(c, nextc))//为什么实用CAS操作呢 这边和Semaphore信号量  是因为存在多线程操作的问题
                    return nextc == 0;
            }
        }
    }

I have annotated the above code, I believe it can be understood at a glance, the method is very simple, mainly two methods that rewrite AQS tryAcquireShared and tryReleaseShared

Internal method

 /**
     * 默认的构造函数
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    /**
     * 共享模式下的AQS获取的资源的方法 响应中断的版本
     */
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    /**
     * 共享模式下 获取资源 带截止时间的
     */
    public boolean await(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    /**
     * 释放同步器State资源 减1
     */
    public void countDown() {
        sync.releaseShared(1);
    }

    /**
     * 返回当前的同步器状态值
     */
    public long getCount() {
        return sync.getCount();
    }

The above is the CountDownLatch method. There is nothing to talk about. It is implemented by calling the method in AQS. This has been explained a lot. I will not say it. I have a clear look at the above ~

CyclicBarrier

CyclicBarrier's class structure is completely different from CountDownLatch. It does not directly inherit the AQS class, but uses a combination of ReentrantLock and Condition to implement the function.

Constructor

First look at the two constructors:

public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;//表示开启Barrier需要的线程数量
        this.count = parties;// 当前还剩余需要开启屏障的线程数量
        this.barrierCommand = barrierAction;//开启屏障后的回调函数
    }

    /**
     * 创建CyclicBarrier   parties表示开启Barrier需要的线程数量
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

After reading the constructor, look at a few methods

reset method

 /**
     * break当前的屏障 并且开启下一个屏障
     */
    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // 破坏当前的屏障
            nextGeneration(); // 开启一个新的屏障
        } finally {
            lock.unlock();
        }
    }
    
     private void breakBarrier() {
        generation.broken = true;//设置当前屏障的broken值,
        count = parties;//重置count值 屏障已经破坏 count值要还原 为下次屏障做准备
        trip.signalAll();//唤醒所有之前在此屏障上等待的线程
    }
    
     private void nextGeneration() {
        trip.signalAll();//唤醒之前所有在屏障上等待的线程
        count = parties;//设置开启屏障的数量
        generation = new Generation();//重置generation 里面的broken默认值false
    }

The breakBarrier and nextGeneration methods do the same thing. The breakBarrier mainly deals with the current barrier. The nextGeneration does to open a new barrier, which is why the CyclicBarrier cycle barrier is called, because it can be turned off and then turned on again.

await method

There are 2 pulic await methods in CyclicBarrier. One is without time waiting, the other is with time waiting. The core is all dowait method.

 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));
    }
   //核心方法dowait
   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)//如果当前的Barrier已经broken 就抛出BrokenBarrierException异常
                throw new BrokenBarrierException();

            if (Thread.interrupted()) {//如果当前线程发生了中断 就执行breakBarrier 然后抛出中断异常
                breakBarrier();
                throw new InterruptedException();
            }

            int index = --count;// 每次执行先减少count值 在判断
            if (index == 0) {  // 说明已经到了打开Barrier的条件
                boolean ranAction = false;//是否执行打开当前屏障成功
                try {
                    final Runnable command = barrierCommand;//开启Barrier的执行的Runnable方法
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();//重置下一个Barrier的条件
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();//如果已经到达了开启Barrier条件 但是没有执行成功 就破坏掉Barrier
                }
            }

            /**
             * loop until
             * tripped, 被最后达到Barrier的线程唤醒
             * broken, 当前的Barrier遭到了破坏
             * interrupted,阻塞等待的线程 被中断 包括当前线程
             * or timed out 或者其中一个等待的线程 超时了
             * */
            for (; ; ) {//这是一个自旋
                try {
                    if (!timed)//是否有等待的时间  如果没有等待的时间 就直接阻塞当前线程 当前线程进入tripConditionQueue中
                        trip.await();
                    else if (nanos > 0L)//如果有等待时间并且等待时间大于0
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {//捕获线程等待期间的中断
                    if (g == generation && !g.broken) {// 如果generation没有发生变化 说明在还在之前的Barrier中 且Barrier没有破坏
                        breakBarrier();//那就执行破坏Barrier的操作  为了唤醒等待的线程
                        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)//唤醒后 发现Barrier已经破坏 抛出BrokenBarrierException异常
                    throw new BrokenBarrierException();

                /**
                 *唤醒后发现等待时的Barrier和唤醒后的Barrier已经不一致了 Barrier已经换代了  返回之前的index
                 * 因为一个线程可以开启多个Barrier 比如reset后会唤醒阻塞的线程  这个时候当前的generation对象是new了一个
                 * 就明显不是一个对象了
                 */
                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {//如果等待的时间已经到了 就破坏屏障 返回TimeoutException
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

The core of the CyclicBarrier method is here.If you are interested, you can compare the source code and try to understand it yourself.

to sum up

Both CyclicBarrier and CountDownLatch can do multiple threads waiting and then start the next action, but the main body of the implementation is different. Through the above code and Demo analysis, the main body of CountDownLatch implementation and the next operation are the creators themselves Threads are not reusable, CyclicBarrier implements the main body of the continued operation is other threads, and can be used repeatedly

Guess you like

Origin www.cnblogs.com/burg-xun/p/12681580.html