多執行緒併發篇——三件兵器
筆者是廣州的java程式員,剛畢業半年,工作之餘寫部落格,如果覺得我的文章寫得不錯,可以關注我的微信公眾號(J2彬彬),裡面會有更多精彩內容。從2018年8月份開始寫部落格,希望日後寫出更多通俗易懂的技術文章與大家一同分享。
talk is cheap,show me the code
JUC其實是屬於執行緒知識的中高階部分,所以也是面試中面試官喜歡問的一塊知識點,這塊知識也能比較看出一個程式設計師的功底,今天筆者就跟大家講講CountDownLatch,CyclicBarrier,Semaphore這些知識點。大家在看到這些知識的時候,千萬不要一上來就摳細節,首先應該弄明白這些知識點時是幹什麼用的,用來解決什麼問題,適合應用於什麼樣的業務場景。如果這些東西都沒搞清楚就學,那麼你學完很快就會忘掉。
之前筆者也發過一些關於JUC方面的知識,但也就是一些Demo程式碼,並沒有細究,今天就是帶領大家:入虎穴,得虎子。
閉鎖CountDownLatch
如果把多個執行緒比喻成運動員,CountDownLatch相當於槍聲發令員,槍聲未響,所有執行緒都處於等待狀態,當CountDownLatch計數器變為0時
相當於槍聲發出,這時所有執行緒一同奔跑。與集齊七顆龍珠便可召喚神龍有異曲同工之妙。
1package com.bingo.thread.juc; 2 3/** 4 * Created with IntelliJ IDEA. 5 * Description: 倒時計數器(也叫閉鎖) 6 * User: bingo 7 * Date: 2018-11-25 8 * Time: 11:16 9 */ 10import java.util.concurrent.CountDownLatch; 11import java.util.concurrent.ExecutorService; 12import java.util.concurrent.Executors; 13 14public class CountDownLatchDemo { 15 16public static void main(String[]args) throws InterruptedException{ 17final CountDownLatch startGate=new CountDownLatch(1); 18final CountDownLatch endGate=new CountDownLatch(5); 19//執行緒池 20ExecutorService exce=Executors.newCachedThreadPool(); 21//建立5個執行緒 22for(int i=1;i<=5;i++){ 23final int num=i; 24Thread thread =new Thread(new Runnable() { 25public void run() { 26try { 27System.out.println(num+"號選手準備就緒,等待裁判員哨聲響起.."); 28//相當於同步鎖Synchronized中的await()方法 29startGate.await(); 30try { 31Thread.sleep((long) (Math.random()*10000)); 32} catch (InterruptedException e) { 33e.printStackTrace(); 34} 35System.out.println(num+"號選手到達終點.."); 36} catch (InterruptedException e) { 37e.printStackTrace(); 38} 39finally{ 40//相當於同步鎖Synchronized中的notify()方法,區別在於countDown需要執行5次後才能喚醒await() 41endGate.countDown(); 42} 43} 44}); 45exce.execute(thread); 46} 47long start=System.nanoTime(); 48System.out.println("裁判員哨聲響起.."); 49Thread.sleep(10000); 50//喚醒執行startGate.await()的執行緒,讓執行緒往下執行 51startGate.countDown(); 52//等待被喚醒,主程式才能繼續往下執行,執行緒中每次執行endGate.countDown()就減1,當為零的時候,主程式往下執行 53endGate.await(); 54long end=System.nanoTime(); 55System.out.println("所有運動員到達終點,耗時:"+(end-start)); 56//關閉執行緒池 57exce.shutdown(); 58} 59} 複製程式碼
執行結果
1裁判員哨聲響起.. 21號選手準備就緒,等待裁判員哨聲響起.. 32號選手準備就緒,等待裁判員哨聲響起.. 43號選手準備就緒,等待裁判員哨聲響起.. 54號選手準備就緒,等待裁判員哨聲響起.. 65號選手準備就緒,等待裁判員哨聲響起.. 73號選手到達終點.. 81號選手到達終點.. 94號選手到達終點.. 105號選手到達終點.. 112號選手到達終點.. 12所有運動員到達終點,耗時:17708083042 13 14Process finished with exit code 0 複製程式碼
CountDownLatch實時系統中的使用場景
- 實現最大的並行性:有時我們想同時啟動多個執行緒,實現最大程度的並行性。例如,我們想測試一個單例類。如果我們建立一個初始計數器為1的CountDownLatch,並讓其他所有執行緒都在這個鎖上等待,只需要呼叫一次countDown()方法就可以讓其他所有等待的執行緒同時恢復執行。
- 開始執行前等待N個執行緒完成各自任務:例如應用程式啟動類要確保在處理使用者請求前,所有N個外部系統都已經啟動和運行了。
- 死鎖檢測:一個非常方便的使用場景是你用N個執行緒去訪問共享資源,在每個測試階段執行緒數量不同,並嘗試產生死鎖。
迴圈屏障CyclicBarrier
CyclicBarrier與CountDownLatch非常相似,但是還是有一定區別。我們先看程式碼。
需求:人們(執行緒)先後到達餐桌上(某個點),但是不能動筷子(等待),所有人到齊才可以吃年夜飯(執行緒到齊才能一同執行)
1package com.bingo.thread.juc; 2 3import java.util.concurrent.BrokenBarrierException; 4import java.util.concurrent.CyclicBarrier; 5 6public class CyclicBarrierDemo { 7 8public static void main(String[] args) { 9final int count = 5; 10final CyclicBarrier barrier = new CyclicBarrier(count, new Runnable() { 11@Override 12public void run() { 13System.out.println("人到齊,大家一起吃年夜飯!"); 14} 15}); 16 17// they do not have to start at the same time... 18for (int i = 0; i < count; i++) { 19new Thread(new Worker(i, barrier)).start(); 20} 21} 22} 23 24class Worker implements Runnable { 25final int id; 26final CyclicBarrier barrier; 27 28public Worker(final int id, final CyclicBarrier barrier) { 29this.id = id; 30this.barrier = barrier; 31} 32 33@Override 34public void run() { 35try { 36System.out.println(this.id + "starts to run !"); 37Thread.sleep((long) (Math.random() * 10000)); 38System.out.println(this.id + "到桌 !"); 39this.barrier.await(); 40} catch (InterruptedException e) { 41e.printStackTrace(); 42} catch (BrokenBarrierException e) { 43e.printStackTrace(); 44} 45} 46} 複製程式碼
執行結果
10starts to run ! 22starts to run ! 31starts to run ! 43starts to run ! 54starts to run ! 64到桌 ! 73到桌 ! 81到桌 ! 92到桌 ! 100到桌 ! 11人到齊,大家一起吃年夜飯! 12 13Process finished with exit code 0 複製程式碼
區別
- CountDownLatch減計數,CyclicBarrier加計數。
- CountDownLatch是一次性的,CyclicBarrier可以重用。
訊號量Semaphore
Semaphore與鎖類似,但是與鎖不同的是,Synchronized是獨佔式的,同一時刻只有一個執行緒能夠操作資源,而Semaphore允許指定的多個執行緒同時操作同個資源。它通過獲取許可,釋放許可來控制多個執行緒操作資源。
需求:假如現在網咖有5臺電腦,但是現在有8個人進入網咖,同一時間只能有5個人上機,而其中三個人主要等到其他人空出電腦的時候才能上機。
這時Semaphore就派上用場了。
1package com.bingo.thread.juc; 2 3import java.util.concurrent.Semaphore; 4 5public class SemaphoreDemo { 6public static void main(String[] args) { 7int N = 8; //學生數 8Semaphore semaphore = new Semaphore(5); //電腦數目 9for(int i=0;i<N;i++) 10new Worker(i,semaphore).start(); 11} 12 13static class Worker extends Thread{ 14private int num; 15private Semaphore semaphore; 16public Worker(int num,Semaphore semaphore){ 17this.num = num; 18this.semaphore = semaphore; 19} 20 21@Override 22public void run() { 23try { 24semaphore.acquire(); 25System.out.println("同學"+this.num+"佔用一臺電腦..."); 26Thread.sleep(2000); 27System.out.println("--同學"+this.num+"離開電腦"); 28semaphore.release(); 29} catch (InterruptedException e) { 30e.printStackTrace(); 31} 32} 33} 34} 複製程式碼
執行結果
1同學0佔用一臺電腦... 2同學1佔用一臺電腦... 3同學2佔用一臺電腦... 4同學3佔用一臺電腦... 5同學4佔用一臺電腦... 6--同學0離開電腦 7--同學1離開電腦 8--同學4離開電腦 9--同學3離開電腦 10同學7佔用一臺電腦... 11--同學2離開電腦 12同學6佔用一臺電腦... 13同學5佔用一臺電腦... 14--同學7離開電腦 15--同學5離開電腦 16--同學6離開電腦 17 18Process finished with exit code 0 複製程式碼
多執行緒是java面試中非常重要一環,算是重點和難點吧,特別是需要結合JVM一起學習,所以今後筆者還會繼續推出相關部落格文章,也希望讀者耐心等待。