聊聊併發(三)——同步輔助類

L發表於2021-11-05

一、概述

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

相關文章