前言:
JUC中提供了很多同步工具類,比如CountDownLatch、CyclicBarrier、Semaphore等,都可以作用同步手段來實現多執行緒之間的同步效果
一、CountDownLatch
1.1、CountDownLatch的使用
CountDownLatch可以理解為是同步計數器,作用是允許一個或多個執行緒等待其他執行緒執行完成之後才繼續執行,比如打dota、LoL或者王者榮耀時,建立了一個五人房,只有當五個玩家都準備了之後,遊戲才能正式開始,否則遊戲主執行緒會一直等待著直到玩家全部準備。在玩家沒準備之前,遊戲主執行緒會一直處於等待狀態。如果把CountDownLatch比做此場景都話,相當於開始定義了匹配遊戲需要5個執行緒,只有當5個執行緒都準備完成了之後,主執行緒才會開始進行匹配操作。
CountDownLatch案例如下:
1 public static void countDownLatchTest() throws Exception{ 2 CountDownLatch latch = new CountDownLatch(5);//定義了需要達到條件都執行緒為5個執行緒 3 new Thread(new Runnable() { 4 @Override 5 public void run() { 6 for (int i=0; i<12; i++){ 7 try { 8 Thread.sleep(1000L); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 long count = latch.getCount(); 16 latch.countDown();/**相當於準備遊戲成功*/ 17 if(count > 0) { 18 System.out.println("執行緒" + Thread.currentThread().getName() + "組隊準備,還需等待" + latch.getCount() + "人準備"); 19 }else { 20 System.out.println("執行緒" + Thread.currentThread().getName() + "組隊準備,房間已滿不可加入"); 21 } 22 } 23 }).start(); 24 } 25 } 26 }).start(); 27 new Thread(new Runnable() { 28 @Override 29 public void run() { 30 try { 31 System.out.println("遊戲房間等待玩家加入..."); 32 /**一直等待到規定數量到執行緒都完全準備之後才會繼續往下執行*/ 33 latch.await(); 34 System.out.println("遊戲房間已鎖定..."); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 } 39 }).start(); 40 System.out.println("等待玩家準備中..."); 41 /**一直等待到規定數量到執行緒都完全準備之後才會繼續往下執行*/ 42 latch.await(); 43 System.out.println("遊戲匹配中..."); 44 }
執行結果如下:
1 等待玩家準備中... 2 遊戲房間等待玩家加入... 3 執行緒Thread-2組隊準備,還需等待4人準備 4 執行緒Thread-3組隊準備,還需等待3人準備 5 執行緒Thread-4組隊準備,還需等待2人準備 6 執行緒Thread-5組隊準備,還需等待1人準備 7 執行緒Thread-6組隊準備,還需等待0人準備 8 遊戲匹配中... 9 遊戲房間已鎖定... 10 執行緒Thread-7組隊準備,房間已滿不可加入 11 執行緒Thread-8組隊準備,房間已滿不可加入 12 執行緒Thread-9組隊準備,房間已滿不可加入 13 執行緒Thread-10組隊準備,房間已滿不可加入 14 執行緒Thread-11組隊準備,房間已滿不可加入 15 執行緒Thread-12組隊準備,房間已滿不可加入 16 執行緒Thread-13組隊準備,房間已滿不可加入
本案例中有兩個執行緒都呼叫了latch.await()方法,則這兩個執行緒都會被阻塞,直到條件達成。當5個執行緒呼叫countDown方法之後,達到了計數器的要求,則後續再執行countDown方法的效果就無效了,因為CountDownLatch僅一次有效。
1.2、CountDownLatch的實現原理
CountDownLatch的實現原理主要是通過內部類Sync來實現的,內部類Sync是AQS的子類,主要是通過重寫AQS的共享式獲取和釋放同步狀態方法來實現的。原始碼如下:
CountDownLatch初始化時需要定義呼叫count的次數,然後每呼叫一次countDown方法都會計數減一,原始碼如下:
1 public CountDownLatch(int count) { 2 if (count < 0) throw new IllegalArgumentException("count < 0"); 3 this.sync = new Sync(count); 4 }
1 public void countDown() { 2 sync.releaseShared(1); 3 }
1 public void await() throws InterruptedException { 2 sync.acquireSharedInterruptibly(1); 3 }
可以看出CountDownLatch的實現邏輯全部都是呼叫內部類Sync的對應方法實現的,Sync原始碼如下:
1 private static final class Sync extends AbstractQueuedSynchronizer { 2 private static final long serialVersionUID = 4982264981922014374L; 3 4 Sync(int count) { 5 /**初始化設定計數值實際就是設定AQS的同步狀態值*/ 6 setState(count); 7 } 8 9 int getCount() { 10 return getState(); 11 } 12 13 protected int tryAcquireShared(int acquires) { 14 return (getState() == 0) ? 1 : -1; 15 } 16 17 /**CountDownLatch的countDown方法的實際執行邏輯*/ 18 protected boolean tryReleaseShared(int releases) { 19 // Decrement count; signal when transition to zero 20 for (;;) { 21 int c = getState(); 22 /**當CountDownLatch的計數值為0時,表示倒計數已完成,則直接返回false*/ 23 if (c == 0) 24 return false; 25 int nextc = c-1; 26 /**通過CAS操作來設定同步狀態自減1操作*/ 27 if (compareAndSetState(c, nextc)) 28 /**返回當前同步狀態值是否為0,只有當狀態值為0時才返回true,否則返回false*/ 29 return nextc == 0; 30 } 31 } 32 }
通過內部類Sync的原始碼可以分析出,CountDownLatch的實現完整邏輯如下:
1、初始化CountDownLatch實際就是設定了AQS的state為計數的值
2、呼叫CountDownLatch的countDown方法時實際就是呼叫AQS的釋放同步狀態的方法,每呼叫一次就自減一次state值
3、呼叫await方法實際就呼叫AQS的共享式獲取同步狀態的方法acquireSharedInterruptibly(1),這個方法的實現邏輯就呼叫子類Sync的tryAcquireShared方法,只有當子類Sync的tryAcquireShared方法返回大於0的值時才算獲取同步狀態成功,
否則就會一直在死迴圈中不斷重試,直到tryAcquireShared方法返回大於等於0的值,而Sync的tryAcquireShared方法只有當AQS中的state值為0時才會返回1,否則都返回-1,也就相當於只有當AQS的state值為0時,await方法才會執行成功,否則
就會一直處於死迴圈中不斷重試。
總結:
CountDownLatch實際完全依靠AQS的共享式獲取和釋放同步狀態來實現,初始化時定義AQS的state值,每呼叫countDown實際就是釋放一次AQS的共享式同步狀態,await方法實際就是嘗試獲取AQS的同步狀態,只有當同步狀態值為0時才能獲取成功。
二、CyclicBarrier
2.1、CyclicBarrier的使用
CyclicBarrier可以理解為一個迴圈同步屏障,定義一個同步屏障之後,當一組執行緒都全部達到同步屏障之前都會被阻塞,直到最後一個執行緒達到了同步屏障之後才會被開啟,其他執行緒才可繼續執行。
還是以dota、LoL和王者榮耀為例,當第一個玩家準備了之後,還需要等待其他4個玩家都準備,遊戲才可繼續,否則準備的玩家會被一直處於等待狀態,只有當最後一個玩家準備了之後,遊戲才會繼續執行。
CyclicBarrier使用案例如下:
1 public static void CyclicBarrierTest() throws Exception { 2 CyclicBarrier barrier = new CyclicBarrier(5);//定義需要達到同步屏障的執行緒數量 3 for (int i=0;i<12;i++){ 4 Thread.sleep(1000L); 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 try { 9 System.out.println("執行緒"+Thread.currentThread().getName()+"組隊準備,當前" + (barrier.getNumberWaiting()+1) + "人已準備"); 10 barrier.await();/**執行緒進入等待,直到最後一個執行緒達到同步屏障*/ 11 System.out.println("執行緒:"+Thread.currentThread().getName()+"開始組隊遊戲"); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } catch (BrokenBarrierException e) { 15 e.printStackTrace(); 16 } 17 } 18 }).start(); 19 } 20 }
執行結果如下:
1 執行緒Thread-0組隊準備,當前1人已準備 2 執行緒Thread-1組隊準備,當前2人已準備 3 執行緒Thread-2組隊準備,當前3人已準備 4 執行緒Thread-3組隊準備,當前4人已準備 5 執行緒Thread-4組隊準備,當前5人已準備 6 執行緒:Thread-4開始組隊遊戲 7 執行緒:Thread-0開始組隊遊戲 8 執行緒:Thread-1開始組隊遊戲 9 執行緒:Thread-2開始組隊遊戲 10 執行緒:Thread-3開始組隊遊戲 11 執行緒Thread-5組隊準備,當前1人已準備 12 執行緒Thread-6組隊準備,當前2人已準備 13 執行緒Thread-7組隊準備,當前3人已準備 14 執行緒Thread-8組隊準備,當前4人已準備 15 執行緒Thread-9組隊準備,當前5人已準備 16 執行緒:Thread-9開始組隊遊戲 17 執行緒:Thread-5開始組隊遊戲 18 執行緒:Thread-7開始組隊遊戲 19 執行緒:Thread-6開始組隊遊戲 20 執行緒:Thread-8開始組隊遊戲 21 執行緒Thread-10組隊準備,當前1人已準備 22 執行緒Thread-11組隊準備,當前2人已準備
本案例中定義了達到同步屏障的執行緒為5個,每當一個執行緒呼叫了barrier.await()方法之後表示該執行緒已達到屏障,此時當前執行緒會被阻塞,只有當最後一個執行緒呼叫了await方法之後,被阻塞的其他執行緒才會被喚醒繼續執行。
另外CyclicBarrier是迴圈同步屏障,同步屏障開啟之後立馬會繼續計數,等待下一組執行緒達到同步屏障。而CountDownLatch僅單次有效。
2.2、CyclicBarrier的實現原理
先看下CyclicBarrier的構造方法
1 public CyclicBarrier(int parties) { 2 this(parties, null); 3 } 4 5 public CyclicBarrier(int parties, Runnable barrierAction) { 6 if (parties <= 0) throw new IllegalArgumentException(); 7 this.parties = parties;//同步屏障總需執行緒數 8 this.count = parties;//當前剩餘需要達到的執行緒數 9 this.barrierCommand = barrierAction; 10 }
CyclicBarrier的構造方法沒有特殊之處,主要是給兩個屬性parties(匯流排程數)、count(當前剩餘執行緒數)進行賦值,這裡需要兩個值的原因是CyclicBarrier提供了重置的功能,當呼叫reset方法重置時就需要將count值再賦值成parties的值
再看下await方法的實現邏輯
1 public int await() throws InterruptedException, BrokenBarrierException { 2 try { 3 //呼叫私有方法dowait方法 4 return dowait(false, 0L); 5 } catch (TimeoutException toe) { 6 throw new Error(toe); // cannot happen 7 } 8 } 9 10 /** 11 * Main barrier code, covering the various policies. 12 */ 13 private int dowait(boolean timed, long nanos) 14 throws InterruptedException, BrokenBarrierException, 15 TimeoutException { 16 //CyclicBarrier有個ReentrantLock屬性的lock 17 final ReentrantLock lock = this.lock; 18 //加鎖操作 19 lock.lock(); 20 try { 21 final Generation g = generation; 22 23 if (g.broken) 24 throw new BrokenBarrierException(); 25 //響應執行緒中斷 26 if (Thread.interrupted()) { 27 breakBarrier(); 28 throw new InterruptedException(); 29 } 30 //count自減操作 31 int index = --count; 32 //判斷當前還需達到同步屏障的執行緒數是否為0 33 if (index == 0) { // tripped 34 boolean ranAction = false; 35 try { 36 //barrierCommand是同步屏障開啟之後需要執行的Runnable物件 37 final Runnable command = barrierCommand; 38 if (command != null) 39 //如果Runnable物件不為空直接執行Runnable執行緒任務 40 command.run(); 41 ranAction = true; 42 //本次同步屏障全部達成,喚醒所有執行緒並開始下一次同步屏障 43 nextGeneration(); 44 return 0; 45 } finally { 46 if (!ranAction) 47 breakBarrier(); 48 } 49 } 50 51 // loop until tripped, broken, interrupted, or timed out 52 for (;;) { 53 try { 54 if (!timed) 55 //呼叫Condition物件的await方法使當前執行緒進入等待狀態 56 trip.await(); 57 else if (nanos > 0L) 58 nanos = trip.awaitNanos(nanos); 59 } catch (InterruptedException ie) { 60 if (g == generation && ! g.broken) { 61 breakBarrier(); 62 throw ie; 63 } else { 64 // We're about to finish waiting even if we had not 65 // been interrupted, so this interrupt is deemed to 66 // "belong" to subsequent execution. 67 Thread.currentThread().interrupt(); 68 } 69 } 70 71 if (g.broken) 72 throw new BrokenBarrierException(); 73 74 if (g != generation) 75 return index; 76 77 if (timed && nanos <= 0L) { 78 breakBarrier(); 79 throw new TimeoutException(); 80 } 81 } 82 } finally { 83 lock.unlock(); 84 } 85 } 86 87 private void nextGeneration() { 88 // signal completion of last generation 89 //喚醒所有執行緒 90 trip.signalAll(); 91 // set up next generation 92 count = parties; 93 generation = new Generation(); 94 }
從原始碼可以看出CyclicBarrier的實現原理主要是通過ReentrantLock和Condition來實現的,主要實現流程如下:
1、建立CyclicBarrier時定義了CyclicBarrier物件需要達到的執行緒數count
2、每當一個執行緒執行了await方法時,需要先通過ReentrantLock進行加鎖操作,然後對count進行自減操作,操作成功則判斷當前count是否為0;
3、如果當前count不為0則呼叫Condition的await方法使當前執行緒進入等待狀態;
4、如果當前count為0則表示同步屏障已經完全,此時呼叫Condition的signalAll方法喚醒之前所有等待的執行緒,並開啟迴圈的下一次同步屏障功能;
5、喚醒其他執行緒之後,其他執行緒繼續執行剩餘的邏輯。
2.3、通過Synchronized和wait/notify實現CyclicBarrier
通過分析瞭解了CyclicBarrier是通過ReentrantLock和Condition來實現的,而ReentrantLock+Condition在使用上基本上等同於Synchronized+wait/notify,既然如此就可以通過Synchronized+wait/notify來自定義一個CyclicBarrier,話不多說,程式碼如下:
public class MyCyclicBarrier { public MyCyclicBarrier(int parties){ this.parties = parties; this.count = parties; } /**當前剩餘數量*/ private int count; /**設定同數量*/ private int parties; /**獲取當前已準備數量*/ public int getNumberWaiting(){ return parties-count; } public int await(){ synchronized (this){ int rest = --count; if(rest==0){ //當剩餘數量為0時表示所有執行緒達到屏障,則重置同步屏障並喚醒所有執行緒 this.count = parties; this.notifyAll(); return 0; } try { //當剩餘數量大於0時,執行緒進入等待狀態 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return count; } /**測試 Main 方法*/ public static void main(String[] args) throws InterruptedException { MyCyclicBarrier barrier = new MyCyclicBarrier(5);//定義需要達到同步屏障的執行緒數量 for (int i=0;i<12;i++){ Thread.sleep(1000L); new Thread(new Runnable() { @Override public void run() { try { System.out.println("執行緒"+Thread.currentThread().getName()+"組隊準備,當前" + (barrier.getNumberWaiting()+1) + "人已準備"); barrier.await(); System.out.println("執行緒:"+Thread.currentThread().getName()+"開始組隊遊戲"); } catch (Exception e) { e.printStackTrace(); } } }).start(); } } }
執行結果如下:
1 執行緒Thread-0組隊準備,當前1人已準備 2 執行緒Thread-1組隊準備,當前2人已準備 3 執行緒Thread-2組隊準備,當前3人已準備 4 執行緒Thread-3組隊準備,當前4人已準備 5 執行緒Thread-4組隊準備,當前5人已準備 6 執行緒:Thread-4開始組隊遊戲 7 執行緒:Thread-3開始組隊遊戲 8 執行緒:Thread-0開始組隊遊戲 9 執行緒:Thread-1開始組隊遊戲 10 執行緒:Thread-2開始組隊遊戲 11 執行緒Thread-5組隊準備,當前1人已準備 12 執行緒Thread-6組隊準備,當前2人已準備 13 執行緒Thread-7組隊準備,當前3人已準備 14 執行緒Thread-8組隊準備,當前4人已準備 15 執行緒Thread-9組隊準備,當前5人已準備 16 執行緒:Thread-9開始組隊遊戲 17 執行緒:Thread-7開始組隊遊戲 18 執行緒:Thread-5開始組隊遊戲 19 執行緒:Thread-6開始組隊遊戲 20 執行緒:Thread-8開始組隊遊戲 21 執行緒Thread-10組隊準備,當前1人已準備 22 執行緒Thread-11組隊準備,當前2人已準備
可以看出實現的效果和CyclicBarrier實現的效果完全一樣
三、Semaphore
3.1、Semaphore的使用
Semaphore字面意思是訊號量,實際可以看作是一個限流器,初始化Semaphore時就定義好了最大通行證數量,每次呼叫時呼叫方法來消耗,業務執行完畢則釋放通行證,如果通行證消耗完,再獲取通行證時就需要阻塞執行緒直到有通行證可以獲取。
比如銀行櫃檯的視窗,一共有5個視窗可以使用,當視窗都被佔用時,後面來的人就需要排隊等候,直到有視窗使用者辦理完業務離開之後後面的人才可繼續爭取。模擬程式碼如下:
1 public static void semaphoreTest() throws InterruptedException { 2 int count = 5; 3 Semaphore semaphore = new Semaphore(count); 4 System.out.println("初始化" + count + "個銀行櫃檯視窗"); 5 for (int i=0;i<10;i++){ 6 Thread.sleep(1000L); 7 new Thread(new Runnable() { 8 @Override 9 public void run() { 10 try { 11 System.out.println("使用者"+Thread.currentThread().getName()+"佔用視窗"); 12 semaphore.acquire(1);//獲取許可證 13 /**使用者辦理業務需要消耗一定時間*/ 14 System.out.println("使用者"+Thread.currentThread().getName()+"開始辦理業務"); 15 Thread.sleep(5000L); 16 semaphore.release();//釋放許可證 17 System.out.println("使用者"+Thread.currentThread().getName()+"離開視窗"); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 }).start(); 23 } 24 }
執行結果如下:
1 初始化5個銀行櫃檯視窗 2 使用者Thread-0佔用視窗 3 使用者Thread-0開始辦理業務 4 使用者Thread-1佔用視窗 5 使用者Thread-1開始辦理業務 6 使用者Thread-2佔用視窗 7 使用者Thread-2開始辦理業務 8 使用者Thread-3佔用視窗 9 使用者Thread-3開始辦理業務 10 使用者Thread-4佔用視窗 11 使用者Thread-4開始辦理業務 12 使用者Thread-0離開視窗 13 使用者Thread-5佔用視窗 14 使用者Thread-5開始辦理業務 15 使用者Thread-1離開視窗 16 使用者Thread-6佔用視窗 17 使用者Thread-6開始辦理業務 18 使用者Thread-2離開視窗 19 使用者Thread-7佔用視窗 20 使用者Thread-7開始辦理業務 21 使用者Thread-3離開視窗 22 使用者Thread-8佔用視窗 23 使用者Thread-8開始辦理業務 24 使用者Thread-4離開視窗 25 使用者Thread-9佔用視窗 26 使用者Thread-9開始辦理業務 27 使用者Thread-5離開視窗 28 使用者Thread-6離開視窗 29 使用者Thread-7離開視窗 30 使用者Thread-8離開視窗 31 使用者Thread-9離開視窗
可以看出前5個執行緒可以直接佔用視窗,但是後5個執行緒需要等待前面的執行緒離開了視窗之後才可佔用視窗。
Semaphore呼叫acquire方法獲取許可證,可以同時獲取多個,但是也需要對應的釋放多個,否則會造成其他執行緒獲取不到許可證的情況。一旦許可證被消耗完,那麼執行緒就需要被阻塞,直到許可證被釋放才可繼續執行。
另外Semaphore還具有公平模式和非公平模式兩種用法,公平模式則遵循FIFO原則先排隊的執行緒先拿到許可證;非公平模式則自行爭取。
3.2、Semaphore實現原理
Semaphore的構造方法
1 public Semaphore(int permits) { 2 //非公平模式 3 sync = new NonfairSync(permits); 4 } 5 6 public Semaphore(int permits, boolean fair) { 7 //公平模式 8 sync = fair ? new FairSync(permits) : new NonfairSync(permits); 9 }
構造方法只有兩個引數,一個是許可證總數量,一個是是否為公平模式;預設是非公平模式
Semaphore的實現全部是通過其內部類Sync來實現了,Sync也是AQS的子類,Semaphore的實現方式基本上和ReentrantLock的實現原理如出一轍。
公平模式實現原理:
1 FairSync(int permits) { 2 //初始化AQS的state的值 3 super(permits); 4 } 5 6 protected int tryAcquireShared(int acquires) { 7 for (;;) { 8 //當首節點的後繼節點不是當前執行緒時直接return -1 9 if (hasQueuedPredecessors()) 10 return -1; 11 //獲取當前AQS的state值 12 int available = getState(); 13 //將state減去需要佔用的許可證數量得到剩餘的許可證數量 14 int remaining = available - acquires; 15 /** 16 * 當remaining<0時返回remaining則表示獲取許可證失敗,會進入AQS的死迴圈不停重試 17 * 當remain>0時,並且CAS成功了則返回remaining,表示獲取許可證成功了 18 * */ 19 if (remaining < 0 || 20 compareAndSetState(available, remaining)) 21 return remaining; 22 } 23 }
公平模式就是噹噹前執行緒是AQS同步佇列首節點的後繼節點時才有權利嘗試獲取共享式的同步狀態,並將同步狀態值減去需要佔用的許可證數量,如果剩餘許可證數量小於0則表示獲取失敗進入AQS的死迴圈不停重試;
如果許可證數量大於0並且CAS設定成功了,則返回剩餘許可證數量表示搶佔許可證成功;
非公平模式實現原理:
看我公平模式的實現基本是就可以猜到非公平模式是如何實現的,只是會少了一步判斷當前節點是否是首節點的後繼節點而已。
1 static final class NonfairSync extends Sync { 2 private static final long serialVersionUID = -2694183684443567898L; 3 4 NonfairSync(int permits) { 5 super(permits); 6 } 7 8 protected int tryAcquireShared(int acquires) { 9 return nonfairTryAcquireShared(acquires); 10 } 11 } 12 13 final int nonfairTryAcquireShared(int acquires) { 14 for (;;) { 15 int available = getState(); 16 int remaining = available - acquires; 17 if (remaining < 0 || 18 compareAndSetState(available, remaining)) 19 return remaining; 20 } 21 }
瞭解完Semaphore的公平模式和非公平模式的佔有許可證的方法,再分析釋放許可證的方法,不過可以先自行猜測下會是如何實現的,既然獲取許可證是通過state欄位不斷減少來實現的,那麼毫無疑問釋放許可證就肯定是不斷給state增加來實現的。
釋放許可證原始碼如下:
1 /**嘗試釋放許可證*/ 2 protected final boolean tryReleaseShared(int releases) { 3 for (;;) { 4 int current = getState(); 5 int next = current + releases; 6 if (next < current) // overflow 7 throw new Error("Maximum permit count exceeded"); 8 if (compareAndSetState(current, next)) 9 return true; 10 } 11 }
Semaphore的釋放許可證實際就是呼叫AQS的共享式釋放同步狀態的方法,然後呼叫內部類Sync重寫的AQS的tryReleaseShared方法,實現邏輯就是不停CAS設定state的值加上需要釋放的數量,直到CAS成功。這裡少了AQS的邏輯解析,有興趣可自行回顧AQS的共享式釋放同步狀態的實現原理。
四、Extra Knowledge
4.1、CountDownLatch 和 CyclicBarrier的區別?
CountDownLatch和CyclicBarrier實現的效果看似都是某個執行緒等待一組執行緒達到條件之後才可繼續執行,但是實際上兩者存在很多區別。
1、CountDownLatch阻塞的是呼叫await()的執行緒,不會阻塞達到條件的執行緒;CyclicBarrier阻塞的是達到同步屏障的所有執行緒
2、CountDownLatch採用倒數計數,定義數量之後,每當一個執行緒達到要求之後就減一;CyclicBarrier是正數計數,當數量達到定義的數量之後就開啟同步屏障
3、CountDownLatch僅單次有效,不可重複使用;CyclicBarrir可以迴圈重複使用
4、CountDownLatch定義的數量和實際執行緒數無關,可以有一個執行緒執行多次countDown();CyclicBarrier定義的數量和實際執行緒數一致,必須由多個執行緒都達到要求執行才行(執行緒呼叫await()方法之後就會被阻塞,想呼叫多次也不行的)
5、CountDownLatch是通過內部類Sync繼承AQS來實現的;CyclicBarrier是通過重入鎖ReentrantLock來實現的
6、CountDownLatch不可重置;CyclicBarrier可以呼叫reset方法進行重置