同步計(jì)數(shù)器 CountDownLatch
1. 前言
本節(jié)帶領(lǐng)大家認(rèn)識(shí)第二個(gè)常用的 Java 并發(fā)工具類之 CountDownLatch。
本節(jié)先介紹 CountDownLatch 工具類表達(dá)的概念和最基本用法,接著通過一個(gè)生活中的例子為大家解釋 CountDownLatch 工具類的使用場(chǎng)合,然后通過簡(jiǎn)單的編碼實(shí)現(xiàn)此場(chǎng)景,最后帶領(lǐng)大家熟悉 CountDownLatch 工具類的其他重要方法。
下面我們正式開始介紹吧。
2. 概念解釋
CountDownLatch 工具類從字面理解為 “倒計(jì)數(shù)鎖”,其內(nèi)部使用一個(gè)計(jì)數(shù)器進(jìn)行實(shí)現(xiàn),計(jì)數(shù)器初始值為線程的數(shù)量。當(dāng)每一個(gè)線程完成自己的任務(wù)后,計(jì)數(shù)器的值就會(huì)減一。當(dāng)計(jì)數(shù)器的值為 0 時(shí),表示所有的線程都已經(jīng)完成了任務(wù),然后在 CountDownLatch 上等待的線程就可以恢復(fù)繼續(xù)執(zhí)行后繼任務(wù)。是不是很抽象,其實(shí)很簡(jiǎn)單,看下面的圖例。
這就是 CountDownLatch 工具類的基本邏輯。概念已經(jīng)了解了,CountDownLatch 工具類最基本的用法是怎樣的呢?看下面。
3. 基本用法
// 創(chuàng)建一個(gè) CountDownLatch 對(duì)象
CountDownLatch countDownLatch = new CountDownLatch(子線程個(gè)數(shù));
// 子線程1開始處理邏輯
...
// 子線程執(zhí)行完所有邏輯進(jìn)行計(jì)數(shù)器減1
countDownLatch.countDown();
// 子線程n開始處理邏輯
...
// 子線程執(zhí)行完所有邏輯進(jìn)行計(jì)數(shù)器減1
countDownLatch.countDown();
// 主線程等待所有子線程執(zhí)行完
countDownLatch.await();
// 主線程繼續(xù)執(zhí)行后繼邏輯
...
是不是很簡(jiǎn)單,CountDownLatch 應(yīng)用在哪些場(chǎng)合比較合適呢?下面我們給出最常用的場(chǎng)景說明。
4. 常用場(chǎng)景
CountDownLatch 經(jīng)常用于某一線程在開始運(yùn)行前等待其他關(guān)聯(lián)線程執(zhí)行完畢的場(chǎng)合。
比如我們制作一張復(fù)雜報(bào)表,報(bào)表的各部分可以安排對(duì)應(yīng)的一個(gè)線程進(jìn)行計(jì)算,只有當(dāng)所有線程都執(zhí)行完畢后,再由最終的報(bào)表輸出線程進(jìn)行報(bào)表文件生成。
下面我們使用 CountDownLatch 實(shí)現(xiàn)這個(gè)例子。假設(shè)這張報(bào)表有 5 個(gè)部分,我們總共安排 5 個(gè)子線程分別計(jì)算,再設(shè)置 1 個(gè)報(bào)表輸出線程用于最終生成報(bào)表文件。請(qǐng)看下面代碼。
5. 場(chǎng)景案例
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
// 創(chuàng)建一個(gè) CountDownLatch 對(duì)象,初始化為 5, 代表需要控制同步的子線程個(gè)數(shù)
static int threadCount = 5;
private static CountDownLatch countDownLatch = new CountDownLatch(threadCount);
// 報(bào)表生成主線程
public static void main(String[] args) throws InterruptedException {
// 定義報(bào)表子線程
for(int i=1; i<=threadCount; i++) {
// 開始報(bào)表子線程處理
new Thread(new Runnable() {
public void run() {
// 模擬報(bào)表數(shù)據(jù)計(jì)算時(shí)間
try {
Thread.sleep(new Random().nextInt(5000));
} catch (Exception e) {}
System.out.println( Thread.currentThread().getName() + "已經(jīng)處理完畢");
countDownLatch.countDown();
}
}, "報(bào)表子線程" + i).start();
}
// 主線程等待所有子線程運(yùn)行完畢后輸出報(bào)表文件
countDownLatch.await();
System.out.println("報(bào)表數(shù)據(jù)已經(jīng)全部計(jì)算完畢,開始生成報(bào)表文件...");
}
}
運(yùn)行上面代碼,我們觀察一下運(yùn)行結(jié)果。
報(bào)表子線程3已經(jīng)處理完畢
報(bào)表子線程5已經(jīng)處理完畢
報(bào)表子線程1已經(jīng)處理完畢
報(bào)表子線程4已經(jīng)處理完畢
報(bào)表子線程2已經(jīng)處理完畢
報(bào)表數(shù)據(jù)已經(jīng)全部計(jì)算完畢,開始生成報(bào)表文件...
觀察結(jié)果,和我們的預(yù)期一致。注意體會(huì) CountDownLatch 提供的多線程共同協(xié)作的模型。
6. 其他方法介紹
除過上面代碼中使用的最基本的 countDown ()、await () 方法之外,還有兩個(gè)方法大家可以了解一下。
-
await (long, TimeUnit) 方法
此方法提供了更靈活的等待參數(shù),可以設(shè)置等待超時(shí)時(shí)間。當(dāng)?shù)却^了設(shè)定的時(shí)限,則不再阻塞直接開始后繼處理。 -
getCount () 方法
調(diào)用此方法,可獲得當(dāng)前計(jì)數(shù)器的數(shù)值,了解整體處理進(jìn)度。
7. 小結(jié)
本節(jié)解釋了 CountDownLatch 的基本邏輯模型,且通過一個(gè)簡(jiǎn)單的例子,介紹了 CountDownLatch 的使用場(chǎng)景和基本用法。希望大家在學(xué)習(xí)過程中,多思考勤練習(xí),早日掌握之。