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

一、概述

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

posted @ 2021-11-05 10:36  Craftsman-L  阅读(315)  评论(0编辑  收藏  举报