1. 程式人生 > >多執行緒併發篇——三件兵器

多執行緒併發篇——三件兵器

  筆者是廣州的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
16    public static void main(String[]args) throws InterruptedException{
17        final CountDownLatch startGate=new CountDownLatch(1);
18        final CountDownLatch endGate=new CountDownLatch(5);
19        //執行緒池
20        ExecutorService exce=Executors.newCachedThreadPool();
21        //建立5個執行緒
22        for(int i=1;i<=5;i++){
23            final int num=i;
24            Thread thread =new Thread(new Runnable() {
25                public void run() {
26                    try {
27                        System.out.println(num+"號選手準備就緒,等待裁判員哨聲響起..");
28                        //相當於同步鎖Synchronized中的await()方法
29                            startGate.await();
30                        try {
31                            Thread.sleep((long) (Math.random()*10000));
32                        } catch (InterruptedException e) {
33                            e.printStackTrace();
34                        }
35                        System.out.println(num+"號選手到達終點..");
36                    } catch (InterruptedException e) {
37                        e.printStackTrace();
38                    }
39                    finally{
40                        //相當於同步鎖Synchronized中的notify()方法,區別在於countDown需要執行5次後才能喚醒await()
41                        endGate.countDown();
42                    }
43                }
44            });
45            exce.execute(thread);
46        }
47        long start=System.nanoTime();
48        System.out.println("裁判員哨聲響起..");
49        Thread.sleep(10000);
50        //喚醒執行startGate.await()的執行緒,讓執行緒往下執行
51        startGate.countDown();
52        //等待被喚醒,主程式才能繼續往下執行,執行緒中每次執行endGate.countDown()就減1,當為零的時候,主程式往下執行
53        endGate.await();
54        long end=System.nanoTime();
55        System.out.println("所有運動員到達終點,耗時:"+(end-start));
56        //關閉執行緒池
57        exce.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
8   public static void main(String[] args) {
9      final int count = 5;
10      final CyclicBarrier barrier = new CyclicBarrier(count, new Runnable() {
11         @Override
12         public void run() {
13            System.out.println("人到齊,大家一起吃年夜飯!");
14         }
15      });
16
17      // they do not have to start at the same time...
18      for (int i = 0; i < count; i++) {
19         new Thread(new Worker(i, barrier)).start();
20      }
21   }
22}
23
24class Worker implements Runnable {
25   final int id;
26   final CyclicBarrier barrier;
27
28   public Worker(final int id, final CyclicBarrier barrier) {
29      this.id = id;
30      this.barrier = barrier;
31   }
32
33   @Override
34   public void run() {
35      try {
36         System.out.println(this.id + "starts to run !");
37         Thread.sleep((long) (Math.random() * 10000));
38         System.out.println(this.id + "到桌 !");
39         this.barrier.await();
40      } catch (InterruptedException e) {
41         e.printStackTrace();
42      } catch (BrokenBarrierException e) {
43         e.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 {
6    public static void main(String[] args) {
7        int N = 8//學生數
8        Semaphore semaphore = new Semaphore(5); //電腦數目
9        for(int i=0;i<N;i++)
10            new Worker(i,semaphore).start();
11    }
12
13    static class Worker extends Thread{
14        private int num;
15        private Semaphore semaphore;
16        public Worker(int num,Semaphore semaphore){
17            this.num = num;
18            this.semaphore = semaphore;
19        }
20
21        @Override
22        public void run() {
23            try {
24                semaphore.acquire();
25                System.out.println("同學"+this.num+"佔用一臺電腦...");
26                Thread.sleep(2000);
27                System.out.println("--同學"+this.num+"離開電腦");
28                semaphore.release();           
29            } catch (InterruptedException e) {
30                e.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一起學習,所以今後筆者還會繼續推出相關部落格文章,也希望讀者耐心等待。