據說微信搜索《Java魚仔》會變動強哦!java
本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦git
(一)概述
資源的分配方式有兩種,一種是獨佔,好比以前講的ReentrantLock,另一種是共享,即咱們今天將要學習的Semaphore、CyclicBarrier以及CountDownLatch。這些都是JUC包中的類。github
(二)Semaphore
Semaphore是信號量的意思,做用是控制訪問特定資源的線程數量。 其核心API爲:面試
semaphore.acquire(); semaphore.release();
這麼說可能比較模糊,下面我舉個例子。微信
Semaphore就比如遊樂園中的某個遊樂設施的管理員,用來控制同時玩這個遊樂設施的人數。好比跳樓機只能坐十我的,就設置Semaphore的permits等於10。ide
每當有一我的來時,首先判斷permits是否大於0,若是大於0,就把一個許可證給這我的,同時本身的permits數量減一。源碼分析
若是permits數量等於0了,其餘人再想進來時就只能排隊了。學習
當一我的玩好以後,這我的把許可證還給Semaphore,permits加1,正在排隊的人再來競爭這一個許可證。ui
下面經過代碼來演示這樣一個場景this
public class SemaphoreTest { public static void main(String[] args) { //建立permits等於2 Semaphore semaphore=new Semaphore(2); //開五個線程去執行PlayGame for (int i = 0; i < 5; i++) { new Thread(new PlayGame(semaphore)).start(); } } static class PlayGame extends Thread{ Semaphore semaphore; public PlayGame(Semaphore semaphore){ this.semaphore=semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"得到一個許可證"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"釋放一個許可證"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
在這裏設置Semaphore的permit等於2,表示同時只有兩個線程能夠執行,而後開五個線程,在執行前經過semaphore.acquire();
獲取permit,執行後經過semaphore.release();
歸還permit。
經過結果能夠觀察到,每次最多隻會有兩個線程執行PlayGame 。
(三)Semaphore原理
3.1 默認非公平鎖
Semaphore默認建立的是一個非公平鎖:
3.2 Semaphore源碼分析
Semaphore的實現方式和ReentrantLock十分相似。
首先定義一個內部類Sync繼承AbstractQueuedSynchronizer
從Sync的構造方法中能夠看到,初始化時設置state等於permits,在講ReentrantLock的時候,state用來存儲重入鎖的次數,在Semaphore中state用來存儲資源的數量。
Semaphore的核心方法是acquire和release,當執行acquire方法時,sync會執行一個獲取一個共享資源的操做:
核心是判斷剩餘數量是否大於0,若是是的話就經過cas操做去獲取資源,不然就進入隊列中等待
當執行release方法時,sync會執行一個將一個共享資源放回去的cas操做
(四)CountDownLatch
countdownlatch可以讓一個線程等待其餘線程工做完成以後再執行。
countdownlatch經過一個計數器來實現,初始值是指定的數量,每當一個線程完成本身的任務後,計數器減一,當計數器爲0時,執行最後的等待線程。
其核心API爲
CountDownLatch.countDown(); CountDownLatch.await();
下面來看代碼示例:
設定countDownLatch初始值爲2,定義兩個線程分別執行對應的方法,方法執行完畢後再執行countDownLatch.countDown();
這兩個方法執行的過程當中,主線程被countDownLatch.await();
阻塞,只有等到其餘線程都執行完畢以後纔可執行。
public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { //設定初始值爲2 CountDownLatch countDownLatch=new CountDownLatch(2); //執行兩個任務 new Thread(new Task1(countDownLatch)).start(); new Thread(new Task2(countDownLatch)).start(); //在兩個任務執行完以後纔會執行await方法以後的代碼 countDownLatch.await(); System.out.println("其他兩個線程執行完以後執行"); } private static class Task1 implements Runnable { private CountDownLatch countDownLatch; public Task1(CountDownLatch countDownLatch) { this.countDownLatch=countDownLatch; } @Override public void run() { System.out.println("執行任務一"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }finally { if (countDownLatch!=null){ //執行完畢後調用countDown countDownLatch.countDown(); } } } } private static class Task2 implements Runnable { private CountDownLatch countDownLatch; public Task2(CountDownLatch countDownLatch) { this.countDownLatch=countDownLatch; } @Override public void run() { System.out.println("執行任務二"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }finally { if (countDownLatch!=null){ //執行完畢後調用countDown countDownLatch.countDown(); } } } } }
效果以下:
(五)CyclicBarrier
柵欄屏障,讓一組線程到達一個屏障(也能夠叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,全部被屏障攔截的線程纔會繼續運行。 其核心API爲:
cyclicBarrier.await();
和countdownlatch的區別在於,countdownlatch是一個線程等待其餘線程執行完畢後再執行,CyclicBarrier是每個線程等待全部線程執行完畢後,再執行。
看代碼,初始化cyclicBarrier爲3,兩個子線程和一個主線程執行完時都會被阻塞在cyclicBarrier.await();
代碼前,等三個線程都執行完畢後再執行接下去的代碼。
public class CyclicBarrierTest { public static void main(String[] args) throws BrokenBarrierException, InterruptedException { CyclicBarrier cyclicBarrier=new CyclicBarrier(3); System.out.println("執行主線程"); new Thread(new Task1(cyclicBarrier)).start(); new Thread(new Task2(cyclicBarrier)).start(); cyclicBarrier.await(); System.out.println("三個線程都執行完畢,繼續執行主線程"); } private static class Task1 implements Runnable { private CyclicBarrier cyclicBarrier; public Task1(CyclicBarrier cyclicBarrier) { this.cyclicBarrier=cyclicBarrier; } @Override public void run() { System.out.println("執行任務一"); try { Thread.sleep(2000); cyclicBarrier.await(); System.out.println("三個線程都執行完畢,繼續執行任務一"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } private static class Task2 implements Runnable { private CyclicBarrier cyclicBarrier; public Task2(CyclicBarrier cyclicBarrier) { this.cyclicBarrier=cyclicBarrier; } @Override public void run() { System.out.println("執行任務二"); try { Thread.sleep(2000); cyclicBarrier.await(); System.out.println("三個線程都執行完畢,繼續執行任務二"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } }
結果以下:
cyclicBarrier還能夠重複執行,而不須要從新去定義。
public static void main(String[] args) throws BrokenBarrierException, InterruptedException { CyclicBarrier cyclicBarrier=new CyclicBarrier(3); //第一次 System.out.println("執行主線程"); new Thread(new Task1(cyclicBarrier)).start(); new Thread(new Task2(cyclicBarrier)).start(); cyclicBarrier.await(); System.out.println("三個線程都執行完畢,繼續執行主線程"); //第二次 System.out.println("執行主線程"); new Thread(new Task1(cyclicBarrier)).start(); new Thread(new Task2(cyclicBarrier)).start(); cyclicBarrier.await(); }
(六)總結
歸根結底,Semaphore、CyclicBarrier、CountDownLatch三個類都是對AQS中資源共享的應用,學懂AQS以後,你會發現JUC包中的類變得不難了。好了,咱們下期再見!