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