一、概述
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