Java高並發編程基礎三大利器之CountDownLatch


引言

上一篇文章我們介紹了AQS的信號量Semaphore《Java高並發編程基礎三大利器之Semaphore》,接下來應該輪到CountDownLatch了。

什么是CountDownLatch

CountDownLatch是通過一個計數器來實現的,計數器的初始值是線程的數量。每當一個線程執行完畢后,計數器的值就減1,當計數器的值為0時,表示所有線程都執行完畢,然后在閉鎖上(調用await方法的線程)等待的線程就可以恢復工作了。

應用場景

CountDownLatch可以用來干什么呢?有什么應用場景?實際項目中有應用的場景嗎?這應該才是大家比較關心的。我們先來看看官網提供的例子是如何進行應用的https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html
官方提供了兩個demo我直接把它轉成了圖片順帶推薦下這個代碼轉圖片的網址https://www.dute.org/code-snapshot 還挺好用的。

官網demo1

The first is a start signal that prevents any worker from proceeding until the driver is ready for them to proceed;
The second is a completion signal that allows the driver to wait until all workers have completed.

  • 第一個開始信號(startSignal)會阻止任何工人(worker )開始工作,在司機到來之前。說白了就是司機沒來工人就不能干活。
  • 第二個是完成信號 (doneSignal),允許司機 Driver 等待,直到所有的工人完成.說白了就是司機要等到所有工人完工為止。
    在這里插入圖片描述

官網demo2

Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await.

另一種典型的用法就是把一個大任務拆分N個部分,讓多個線程(Worker)執行,每個線程(Worker)執行完自己的部分計數器就減1,當所有子部分都完成后,Driver 才繼續向下執行才繼續執行。
就好比富士康手機加工的流水線一樣,組裝一步手機需要一條條的流水線來相互配合完成。一條條流水線(Worker),每條線都干自己的活。有的流水線是貼膜的,有的流水線是打螺絲的,有的流水線是質檢的、有的流水線充電的、有的流水線貼膜的。等這些流水線都干完了就把一部手機組裝完成了。

在這里插入圖片描述
上面兩個就是官方提供的demo,下面我再來兩個我們平時開發中可以用到的栗子:

多個線程等待:模擬並發,讓並發線程一起執行。

有時候我們寫了接口想去壓測下它,看看它的最大並發數大概是多少。當然我們可以使用Jmeter來進行壓測,但是有時候我們不想去下載工具,其實就可以借助CountDownLatch來實現。

/**
 * @author: 公眾號:java金融
 */
public class TestCountDownLatch1 {
     public static void main(String[] args) throws InterruptedException {
          CountDownLatch countDownLatch = new CountDownLatch(1);
          for (int i = 0; i < 5; i++) {
               new Thread(() -> {
                    try {
                         //所有請求都阻塞在這,等待
                         countDownLatch.await();
                         // 調用測試接口
                         System.out.println(Thread.currentThread().getName() + "開始執行……");
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    }
               }).start();
          }
          // 讓請求都准備好
          Thread.sleep(2000);
          // 讓所有請求統一請求
          countDownLatch.countDown();
     }
}

我們通過CountDownLatch.await(),讓多個參與者線程啟動后阻塞等待,然后在主線程 調用CountDownLatch.countdown() 將計數減為0,讓所有線程一起往下執行;以此實現了多個線程在同一時刻並發執行,來模擬並發請求的目的。

單個線程等待:多個線程(任務)完成后,進行匯總合並

/**
 * @author: 公眾號:java金融
 */
public class TestCountDownLatch1 {
     public static void main(String[] args) throws InterruptedException {
          int count = 3;
          CountDownLatch countDownLatch = new CountDownLatch(count);
          for (int i = 0; i < count; i++) {
               final int index = i;
               new Thread(() -> {
                    try {
                         Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000));
                         System.out.println("finish" + index + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    }finally{
                        countDownLatch.countDown();
                    }
               }).start();
          }
          countDownLatch.await();// 主線程在阻塞,當計數器==0,就喚醒主線程往下執行。
          System.out.println("主線程:在所有任務運行完成后,進行結果匯總");
     }
}

這種場景應該是用的最多了,比如我們打開一個電商的個人中心頁面,我們需要調用,用戶信息接口、用戶訂單接口、用戶會員信息等接口,然后合並后一起給到前端,假設每個接口最長耗時為1s,如果我們同步調用的話最大耗時時間是3s,如果我們采用異步調用然后合並結果,所以最大的耗時時間是3s。每個接口調用返回數據后調用countDown方法,讓計數器進行減1,當把計數器減為0時的這個線程會去喚醒主線程,讓它繼續往下走。

CountDownLatch 實現原理

CountDownLatch是通過AQSstate字段來實現的一個計數器,計數器的初始值(state的值)為new CountDownLatch設置的數量,每次調用countDown的時候,state的值會進行減1,最后某個線程將state值減為0時,會把調用了await()進行阻塞等待的線程進行喚醒。CountDownLatch重寫了tryReleaseShared這個方法,只有當state這個字段被設置為0時,也就是tryReleaseShared返回true的情況就會執行doReleaseShared方法,把調用了await的線程進行喚醒。

  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
 protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

CountDownLatch的其他源碼就不進行分析了,
相信看了這兩篇文章《Java高並發編程基礎之AQS》《Java高並發編程基礎三大利器之Semaphore》再來看這個還是比較輕松的。

總結

  • CountDownLatch不能重新初始化或者修改CountDownLatch內部計數器的值。
  • CountDownLatchSemaphore在使用AQS的方式上很相似,在同步狀態中都是保存的是當前的計數值。
  • CountDownLatch的作用就是允許一個或多個線程等待其他線程完成操作,看起來有點類似join() 方法,但其提供了比 join() 更加靈活的API。
  • CountDownLatch可以手動控制在n個線程里調用ncountDown()方法使計數器進行減一操作,也可以在一個線程里調用n次執行減一操作。
  • join() 的實現原理是不停檢查join線程是否存活,如果 join 線程存活則讓當前線程永遠等待。所以兩者之間相對來說還是CountDownLatch使用起來較為靈活。

結束

  • 由於自己才疏學淺,難免會有紕漏,假如你發現了錯誤的地方,還望留言給我指出來,我會對其加以修正。
  • 如果你覺得文章還不錯,你的轉發、分享、贊賞、點贊、留言就是對我最大的鼓勵。
  • 感謝您的閱讀,十分歡迎並感謝您的關注。

站在巨人的肩膀上摘蘋果:
https://javajr.cn/
https://zhuanlan.zhihu.com/p/148231820


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM