zoukankan      html  css  js  c++  java
  • CountDownLatch和CyclicBarrier和Semaphore最通俗形象解释

    应该还有好多同学对这三个的区别比较模糊,网络上其他文章说的也比较专业化。所以我在这里举个例子说明这三个的区别。

    我们假定有一场百米比赛,比赛包括十个运动员和一个裁判,每个运动员和每个裁判都是一个线程,那么:

    CountDownLatch应用场景:教练需要在终点等待运动员,等所有运动员都达到终点了才可以宣布比赛结束

    private class CountDownLatchTask implements Runnable {
    
        private final CountDownLatch countDownLatch;
    
        private CountDownLatchTask(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() +":起跑");
            try {
                Thread.sleep(Math.round(Math.floor(Math.random()*3000)));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() +":我在奔跑过程中被打断了");
            }
            System.out.println(Thread.currentThread().getName() +":到达终点");
            countDownLatch.countDown();
        }
    }
    
    @Test
    public void TestCountDownWatch() {
        final int threadCount = 10;
    
        System.out.println("裁判:啪!比赛开始");
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(new CountDownLatchTask(countDownLatch), "运动员" + i).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            System.out.println("裁判:等待比赛结果过程中我被打断了");
        }
        System.out.println("裁判:比赛结束");
    }

    我们定义了一个CountDownLatch,相当于一个计数器,计数大小当然和运动员数量是一致的。所有运动员起跑后,教练则等待计数器变为0(执行CountDownLatch.await),运动员到达终点后会将这个计数器-1(执行CountDownLatch.countDown)。当计数器变为0时教练等待结束宣布比赛结束。

    裁判:啪!比赛开始
    运动员0:起跑
    运动员1:起跑
    运动员3:起跑
    运动员5:起跑
    运动员7:起跑
    运动员2:起跑
    运动员9:起跑
    运动员6:起跑
    运动员4:起跑
    运动员8:起跑
    运动员1:到达终点
    运动员2:到达终点
    运动员9:到达终点
    运动员7:到达终点
    运动员6:到达终点
    运动员0:到达终点
    运动员8:到达终点
    运动员5:到达终点
    运动员4:到达终点
    运动员3:到达终点
    裁判:比赛结束

    CyclicBarrier应用场景:这不是一场普通的比赛,要求所有运动员跑到80m的地方先等待,直到所有的运动员都到达80m的时候再重新起跑,直到到达终点

    private class CyclicBarrierTask implements Runnable {
    
        private final CyclicBarrier cyclicBarrier;
    
        private CyclicBarrierTask(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() +":起跑");
            try {
                Thread.sleep(Math.round(Math.floor(Math.random()*3000)));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() +":我在奔跑过程中被打断了");
            }
            System.out.println(Thread.currentThread().getName() +":到达80米处");
            try {
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() +":我在等待过程中被打断了");
            } catch (BrokenBarrierException e) {
                System.out.println(Thread.currentThread().getName() +":我被通知不需要等待了");
            }
            try {
                Thread.sleep(Math.round(Math.floor(Math.random()*1000)));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() +":我在奔跑过程中被打断了");
            }
            System.out.println(Thread.currentThread().getName() +":到达终点");
        }
    }
    
    @Test
    public void TestCyclicBarrier() throws InterruptedException {
        final int threadCount = 10;
    
        System.out.println("裁判:啪!比赛开始");
        final CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(new CyclicBarrierTask(cyclicBarrier), "运动员" + i).start();
        }
    
        Thread.sleep(5000);
        System.out.println("裁判:哦哦,比赛结束了啊");
    }

    我们定义了一个CyclicBarrier,它也相当于一个计数器,计数大小等于运动员人数。当运动员到达80米开始等待其他运动员(执行CyclicBarrier.await,CyclicBarrier在执行await的时候会将内部的计数器-1),直到所有运动员到达80米时计数器变为0则所有运动员解除等待重新起跑,直到跑到终点。

    裁判:啪!比赛开始
    运动员0:起跑
    运动员4:起跑
    运动员6:起跑
    运动员7:起跑
    运动员1:起跑
    运动员2:起跑
    运动员3:起跑
    运动员5:起跑
    运动员9:起跑
    运动员8:起跑
    运动员2:到达80米处
    运动员3:到达80米处
    运动员7:到达80米处
    运动员5:到达80米处
    运动员0:到达80米处
    运动员4:到达80米处
    运动员9:到达80米处
    运动员6:到达80米处
    运动员8:到达80米处
    运动员1:到达80米处
    运动员5:到达终点
    运动员4:到达终点
    运动员9:到达终点
    运动员1:到达终点
    运动员6:到达终点
    运动员7:到达终点
    运动员2:到达终点
    运动员8:到达终点
    运动员3:到达终点
    运动员0:到达终点
    裁判:哦哦,比赛结束了啊

    Semaphore应用场景:这也不是一场普通的比赛,共有10个运动员参加比赛但是却只有3个赛道,所以没有抢到赛道的运动员只能等到赛道上的运动员跑完后再起跑。

    private class SemaphoreTask implements Runnable {
    
        private final Semaphore semaphore;
    
        private SemaphoreTask(Semaphore semaphore) {
            this.semaphore = semaphore;
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() +":裁判,有跑道了吗?");
            boolean canStart = semaphore.tryAcquire();
            if (!canStart) {
                System.out.println(Thread.currentThread().getName() +":那我再等等");
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() +":我在等待过程中被打断了");
                }
            }
            System.out.println(Thread.currentThread().getName() +":起跑");
            try {
                Thread.sleep(Math.round(Math.floor(Math.random()*3000)));
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() +":我在奔跑过程中被打断了");
            }
            System.out.println(Thread.currentThread().getName() +":到达终点");
            semaphore.release();
        }
    }
    
    @Test
    public void TestSemaphore() throws InterruptedException {
        final int threadCount = 10;
        final int semaphoreCount = 3;
    
        System.out.println("裁判:啪!比赛开始");
        final Semaphore semaphore = new Semaphore(semaphoreCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(new SemaphoreTask(semaphore), "运动员" + i).start();
        }
    
        Thread.sleep(15000);
        System.out.println("裁判:哦哦,比赛结束了啊");
    }

    我们定义了一个Semaphore作为赛道,运动员可以询问有赛道了吗并立即获得应答(执行Semaphore.tryAcquire),也可以直接申请赛道并等待(执行Semaphore.acquire),直到获取赛道再起跑。运动员跑到终点后要主动释放报道(执行Semaphore.release),以便其他运动员申请跑到。

    裁判:啪!比赛开始
    运动员0:裁判,有跑道了吗?
    运动员1:裁判,我可以跑了吗?
    运动员1:起跑
    运动员2:裁判,我可以跑了吗?
    运动员2:起跑
    运动员0:起跑
    运动员4:裁判,我可以跑了吗?
    运动员4:那我再等等
    运动员5:裁判,我可以跑了吗?
    运动员5:那我再等等
    运动员6:裁判,我可以跑了吗?
    运动员3:裁判,我可以跑了吗?
    运动员3:那我再等等
    运动员8:裁判,我可以跑了吗?
    运动员8:那我再等等
    运动员6:那我再等等
    运动员9:裁判,我可以跑了吗?
    运动员7:裁判,我可以跑了吗?
    运动员9:那我再等等
    运动员7:那我再等等
    运动员0:到达终点
    运动员4:起跑
    运动员4:到达终点
    运动员5:起跑
    运动员2:到达终点
    运动员3:起跑
    运动员1:到达终点
    运动员8:起跑
    运动员5:到达终点
    运动员6:起跑
    运动员8:到达终点
    运动员9:起跑
    运动员9:到达终点
    运动员7:起跑
    运动员7:到达终点
    运动员6:到达终点
    运动员3:到达终点
    裁判:哦哦,比赛结束了啊

    总结:

    • CountDownLatch:1. 是谁在等待?是裁判在等待。2.为什么要等待?要等待所有运动员到终点才能宣布比赛结束。
    • CyclicBarrier:1. 是谁在等待?是运动员在等待。2.为什么要等待?要等所有运动员都到80米处才可以继续跑。
    • Semaphore1. 是谁在等待?是运动员在等待。2.为什么要等待?要有空余赛道的时候才可以起跑。

    其他:

    1. CountDownLatch和CyclicBarrier的await方法还有一种重载形式:
      public boolean await(long timeout, TimeUnit unit)

      即执行等待的时候设置等待时间,超过此时间则放弃等待执行后续代码。

    2. CountDownLatch不可重用,CyclicBarrier是可重用的
    3. CyclicBarrier的构造方法还有一种重载形式:
      public CyclicBarrier(int parties, Runnable barrierAction)

      当CyclicBarrier的计数器减至0的时候,会直接调用barrierAction的run方法,注意:是直接调用barrierAction的run方法,而不是建立一个新的线程执行barrierAction,详见源码片段:

      final Runnable command = barrierCommand;
      if (command != null)
           command.run();
    4. Semaphore的tryAccquire也有其他重载形式:
      public boolean tryAcquire(); // 申请一个许可(跑道)并立即返回
      public boolean tryAcquire(long timeout, TimeUnit unit); // 申请一个许可并等待一段时间,要么申请到了返回true,要么等待超时了返回false
      public boolean tryAcquire(int permits); // 申请多个个许可(跑道)并立即返回
      public boolean tryAcquire(int permits, long timeout, TimeUnit unit); // 申请多个许可并等待一段时间,要么申请到了返回true,要么等待超时了返回false

      Semaphore的accquire也有其他重载形式:

      public void acquire(int permits)  // 申请多个许可(跑道)并一直等待下去

      Semaphore的release也有其他重载形式:

      public void release(int permits)  // 释放多个许可(跑道)
  • 相关阅读:
    LeetCode【709. 转换成小写字母】
    静态方法、变量与实例方法、变量之比较心得
    用户控件自定义 DependencyProperty 属性使用教程
    TypeLoadException: 未能从程序集“ECS.GUI.Define, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中加载类型“ECS.GUI.Define.ArmgAimPos”,因为它在 4 偏移位置处包含一个对象字段,该字段已由一个非对象字段不正确地对齐或重叠
    ABP 框架实战系列(三)-领域层深入篇
    ABP 框架实战系列(二)- 领域层介绍篇
    ABP框架实战系列(一)-持久层介绍篇
    EF Core 基础知识
    EF Core Migration 报错:An error occurred using the connection to database '' on server '10.28.253.2'
    2020年总结:互联网思维下的工业软件开发
  • 原文地址:https://www.cnblogs.com/LOVE0612/p/9856536.html
Copyright © 2011-2022 走看看