• 聊聊并发(三)——同步辅助类


    一、概述

    1、介绍

      JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为:
      CountDownLatch:减少计数。减一计数器。
      CyclicBarrier:循环栅栏。加一计数器。
      Semaphore:信号灯。

      脑图:https://www.processon.com/view/link/61849ba4f346fb2ecc4546e5

    二、CountDownLatch(闭锁)

    1、班长关门问题

      场景一:6 个同学陆续离开教室后,班长才可以关门。
      代码示例:

     1 public class CountDownLatchDemo {
     2     public static void main(String[] args) {
     3         // 设置一个计数器 为 6
     4         CountDownLatch latch = new CountDownLatch(6);
     5 
     6         // 开启6个线程,来模拟6个同学
     7         for (int i = 1; i <= 6; i++) {
     8             new Thread(() -> {
     9                 try {
    10                     // 生成 5s 以内的随机数,这里仅仅只是让打印更生动.
    11                     Thread.sleep(new Random().nextInt(5) * 1000);
    12                 } catch (InterruptedException e) {
    13                     e.printStackTrace();
    14                 }
    15 
    16                 System.out.println(Thread.currentThread().getName() + "离开教室了~");
    17 
    18                 // 计数器 -1
    19                 latch.countDown();
    20             }, i + " 号同学").start();
    21         }
    22 
    23         // 这里main线程模拟班长.班长要等上面6个线程都执行完,才执行.
    24         // 当计数器为0,即上面 6 个线程都执行完.因await方法阻塞的线程会被唤醒,继续执行.
    25         try {
    26             latch.await();
    27         } catch (InterruptedException e) {
    28             e.printStackTrace();
    29         }
    30 
    31         System.out.println("班长关门了~");
    32     }
    33 }
    34 
    35 // 可能的一种结果
    36 5 号同学离开教室了~
    37 3 号同学离开教室了~
    38 2 号同学离开教室了~
    39 6 号同学离开教室了~
    40 1 号同学离开教室了~
    41 4 号同学离开教室了~
    42 班长关门了~

    2、裁判运动员问题

      场景二:田径运动会上,起跑前所有运动员等待裁判发枪声为准开始比赛。典型的多个线程等待一个线程。
      代码示例:

     1 public class CountDownLatchDemo {
     2     public static void main(String[] args) {
     3         // 设置一个计数器 为 1
     4         CountDownLatch latch = new CountDownLatch(1);
     5 
     6         // 开启6个线程,来模拟6个运动员
     7         for (int i = 1; i <= 6; i++) {
     8             new Thread(() -> {
     9                 try {
    10                     latch.await();
    11                 } catch (InterruptedException e) {
    12                     e.printStackTrace();
    13                 }
    14 
    15                 System.out.println(Thread.currentThread().getName() + "起跑~");
    16 
    17             }, i + " 号运动员").start();
    18         }
    19 
    20         System.out.println("裁判发出枪声,比赛开始~");
    21         // 计数器减1变为0.因await方法阻塞的线程会被唤醒,继续执行.
    22         latch.countDown();
    23     }
    24 }
    25 
    26 // 可能的一种结果
    27 裁判发出枪声,比赛开始~
    28 2 号运动员起跑~
    29 1 号运动员起跑~
    30 3 号运动员起跑~
    31 4 号运动员起跑~
    32 5 号运动员起跑~
    33 6 号运动员起跑~

      场景三:田径运动会上,终点处,计时裁判需要等待所有运动员到达终点,才能宣布本次比赛结束。典型的一个线程等待多个线程。
      代码示例:

     1 public class CountDownLatchDemo {
     2     public static void main(String[] args) {
     3         // 设置一个计数器 为 6
     4         CountDownLatch latch = new CountDownLatch(6);
     5 
     6         // 开启6个线程,来模拟6个运动员
     7         for (int i = 1; i <= 6; i++) {
     8             new Thread(() -> {
     9                 try {
    10                     // 生成 5s 以内的随机数,这里仅仅只是让打印更生动.
    11                     Thread.sleep(new Random().nextInt(5) * 1000);
    12                 } catch (InterruptedException e) {
    13                     e.printStackTrace();
    14                 }
    15 
    16                 System.out.println(Thread.currentThread().getName() + "达到终点~");
    17                 
    18                 // 计数器-1
    19                 latch.countDown();
    20             }, i + " 号运动员").start();
    21         }
    22 
    23         try {
    24             // 主线程在这里阻塞,当latch的计数器减为0,才会被唤醒,继续执行.
    25             latch.await();
    26         } catch (InterruptedException e) {
    27             e.printStackTrace();
    28         }
    29         System.out.println("所有运动员达到,裁判宣布比赛结束~");
    30     }
    31 }
    32 
    33 // 可能的一种结果
    34 1 号运动员达到终点~
    35 2 号运动员达到终点~
    36 6 号运动员达到终点~
    37 4 号运动员达到终点~
    38 3 号运动员达到终点~
    39 5 号运动员达到终点~
    40 所有运动员达到,裁判宣布比赛结束~

    三、CyclicBarrier(循环栅栏)

     1 // 构造器
     2 public CyclicBarrier(int parties, Runnable barrierAction) {
     3     if (parties <= 0) throw new IllegalArgumentException();
     4     this.parties = parties;
     5     this.count = parties;
     6     this.barrierCommand = barrierAction;
     7 }
     8 
     9 int parties:目标障碍数
    10 Runnable barrierAction:达到目标障碍数后,需要执行的方法.

      每执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行目标方法。可以将 CyclicBarrier 理解为加一计数器。

    1、七龙珠收集问题

      代码示例:

     1 public class CyclicBarrierDemo {
     2     // 召唤神龙
     3     private final static int NUM = 7;
     4 
     5     public static void main(String[] args) {
     6 
     7         CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM, () -> {
     8             // 当栅栏数到达7时,执行此方法
     9             System.out.println("集齐" + NUM + "颗龙珠,召唤神龙~");
    10         });
    11 
    12         // 开启7个线程,去收集龙珠
    13         for (int i = 1; i <= 7; i++) {
    14             new Thread(() -> {
    15                 try {
    16                     try {
    17                         // 生成 5s 以内的随机数,这里仅仅只是让打印更生动.
    18                         Thread.sleep(new Random().nextInt(5) * 1000);
    19                     } catch (InterruptedException e) {
    20                         e.printStackTrace();
    21                     }
    22 
    23                     System.out.println(Thread.currentThread().getName() + "收集到了~");
    24 
    25                     // 栅栏数 +1
    26                     cyclicBarrier.await();
    27                 } catch (Exception e) {
    28                     e.printStackTrace();
    29                 }
    30             }, i + " 星龙珠").start();
    31         }
    32     }
    33 }
    34 
    35 // 可能的一种结果
    36 6 星龙珠收集到了~
    37 7 星龙珠收集到了~
    38 2 星龙珠收集到了~
    39 3 星龙珠收集到了~
    40 1 星龙珠收集到了~
    41 5 星龙珠收集到了~
    42 4 星龙珠收集到了~
    43 集齐7颗龙珠,召唤神龙~

    四、Semaphore(信号灯)

    1、介绍

      一个计数信号量。在概念上,信号量维持一组许可证。如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。每个release()添加许可证,潜在地释放阻塞获取方。但是,没有使用实际的许可证对象。Semaphore只保留可用数量的计数,并相应地执行。使用 acquire() 方法获得许可证,release() 方法释放许可。
      理解:就是多个线程一起抢多把锁。

    2、抢车位问题

      代码示例:6辆车抢2个车位

     1 public class SemaphoreDemo {
     2     public static void main(String[] args) {
     3         // 设置 2 个车位
     4         Semaphore semaphore = new Semaphore(2);
     5 
     6         // 开启6个线程,来模拟6辆车
     7         for (int i = 1; i <= 6; i++) {
     8             new Thread(() -> {
     9                 try {
    10                     // 1.获取许可证.表示抢到了车位
    11                     semaphore.acquire();
    12 
    13                     System.out.println(Thread.currentThread().getName() + " 抢到了车位,开始停车~");
    14                     // 生成5s以内的随机数,表示停车了 time 秒
    15                     final long time = new Random().nextInt(5) * 1000;
    16                     Thread.sleep(time);
    17 
    18                     System.out.println(Thread.currentThread().getName() + " 停车了 " + time / 1000 + " 秒,开走了~");
    19                     // 2.释放许可证.表示车开走了
    20                     semaphore.release();
    21                 } catch (Exception e) {
    22                     e.printStackTrace();
    23                 }
    24             }, i + " 号车").start();
    25         }
    26     }
    27 }
    28 
    29 // 可能的一种结果
    30 1 号车 抢到了车位,开始停车~
    31 2 号车 抢到了车位,开始停车~
    32 2 号车 停车了 3 秒,开走了~
    33 3 号车 抢到了车位,开始停车~
    34 1 号车 停车了 4 秒,开走了~
    35 4 号车 抢到了车位,开始停车~
    36 4 号车 停车了 1 秒,开走了~
    37 5 号车 抢到了车位,开始停车~
    38 3 号车 停车了 2 秒,开走了~
    39 6 号车 抢到了车位,开始停车~
    40 6 号车 停车了 3 秒,开走了~
    41 5 号车 停车了 4 秒,开走了~

      这里,Semaphore的构造器参数是2,表示有2个许可证。所以,可以同时停下2辆车。结果不难分析。

      参考文档:https://www.matools.com/api/java8

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    SpringBoot Rabbitmq接收消息
    SpringBoot Rabbitmq发送消息
    Rabbitmq 介绍
    Eureka 使用Spring cloud config 管理中心启动
    Spring Cloud Config 搭建Config 服务
    Eureka 向Server中注册服务
    Eureka 配置
    给定一些分割符,给定一个待分割的字符串,打印出分割之后最长的字符串
    给定N个整数,乱序,分行打印每个数字和其位置
    给定一个整数,求它的二进制表示中有多少个1
  • 原文地址:https://www.cnblogs.com/originator/p/15512042.html
Copyright © 2020-2023  润新知