Java之CountDownLatch原理全面解析
CountDownLatch原理解析
1. demo展示
代碼邏輯展示了主線程中創(chuàng)建2個子線程分別去執(zhí)行任務(wù),主線程等2個子線程執(zhí)行完畢后,再接著執(zhí)行下面的代碼;
常用場景:
分別計算,匯總結(jié)果。如,多個線程分別解析excel中的sheet,等待全部解析完畢后匯總結(jié)果;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo {
//定義一個倒計時閂鎖
static CountDownLatch c = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是線程1");
//釋放一個
c.countDown();
}).start();
new Thread(() -> {
try {
TimeUnit.MICROSECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是線程2");
//釋放一個
c.countDown();
}).start();
System.out.println("我是主線程,我要等那兩個線程執(zhí)行完畢...");
//等待倒計時為0
c.await();
System.out.println("我是主線程,那兩個線程都執(zhí)行完了");
}
}輸出:
我是主線程,我要等那兩個線程執(zhí)行完畢...
我是線程2
我是線程1
我是主線程,那兩個線程都執(zhí)行完了
2. 原理解析
1.先看構(gòu)造函數(shù)new CountDownLatch(2)做了什么?
這是初始化了AQS子類,并將AQS的狀態(tài)state設(shè)置為傳入的2;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}2.看c.countDown()做了什么?
它釋放了一個共享鎖狀態(tài),也就是state減1;
public void countDown() {
sync.releaseShared(1);
}3.再看c.await()做了什么?
await方法是CounDownLatch中定義的,它調(diào)用了其內(nèi)部類Sync(也是AQS的子類)的獲取共享鎖的方法acquireSharedInterruptibly;

acquireSharedInterruptibly方法中調(diào)用了CountDownLatch內(nèi)部類Sync中實現(xiàn)的獲取共享鎖的方法tryAcquireShared,返回值不小0就算獲取到了鎖,await方法就能返回了,如果返回值小于0將會進入阻塞等待;

CountDownLatch內(nèi)部類Sync中tryAcquireShared的實現(xiàn)很簡單,只要state=0就返回1,否則返回-1;上面說了返回一個不小于0的數(shù)字,c.await()就相當于獲取到了鎖,就可以返回了,主線程就可以繼續(xù)執(zhí)行了。

通過上面分析,每次c.countDown(),就會將state減1,state=0的時候主線程恢復(fù)執(zhí)行;
Java CountDownLatch學(xué)習(xí)總結(jié)
來源包
同為 java.util.concurrent 下的,即也是并發(fā)多線程相關(guān)下的類,直譯 “倒計時鎖存器”,一般用于多線程場景,單一的線程也可以,用于等待多個任務(wù)完成后再執(zhí)行其他操作;
提供方法
await()
- 導(dǎo)致當前線程等待,直到鎖存器倒數(shù)到零,除非該線程是{@linkplain Thread35;interrupt interrupted}即被打斷狀態(tài)。
- 如果當前計數(shù)為零,則此方法立即返回。
- 如果當前計數(shù)大于零,則當前線程將出于線程調(diào)度目的被禁用,并處于休眠狀態(tài),直到發(fā)生以下兩種情況之一:
- 由于調(diào)用{@link#countDown}方法,計數(shù)達到零;或者其他線程{@linkplain thread#中斷}當前線程。
如果當前線程:
- 在進入此方法時設(shè)置了其中斷狀態(tài);或者
- 在等待時{@linkplain Thread#interrupt interrupted},
- 則拋出{@link InterruptedException},并清除當前線程的中斷狀態(tài)。
簡單說就是當使用了這個方法后當前這一個線程將進入等待狀態(tài),直到計數(shù)器被減到0或者當前線程被中斷,計數(shù)器被減到0后,所有等待的線程將被喚醒繼續(xù)向下執(zhí)行
await(long timeout, TimeUnit unit)
同上,但是指定了等待的超時時間,即線程除了上方兩種被喚醒的情況下,等待到超時時間后也會被喚醒
countDown():當前計數(shù)器減一,如果如果減到 0 則喚醒所有等待在這個 CountDownLatch 上的線程。getCount():獲取當前計數(shù)的數(shù)值
業(yè)務(wù)書寫示例
即將需要一會兒處理的業(yè)務(wù) list 設(shè)置為計數(shù)器的大小,
然后對里面的業(yè)務(wù)數(shù)據(jù)執(zhí)行異步操作,處理業(yè)務(wù)過程中不論是否有異常都需要對計數(shù)器減一,最終使用 await 等待所有任務(wù)執(zhí)行完成,執(zhí)行完成后,將進入后續(xù)處理
? ? ? ? ? ? final CountDownLatch latch = new CountDownLatch(lists.size());
? ? ? ? ? ??
? ? ? ? ? ? for (List<JSONObject> item: lists) {
? ? ? ? ? ? ? ? executor.submit(new Runnable() {
? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? ? ? // ....... 業(yè)務(wù)處理
? ? ? ? ? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? // 異常處理
? ? ? ? ? ? ? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? ? ? ? ? ? ? latch.countDown();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? latch.await();
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? log.error("線程被中斷", e);
? ? ? ? ? ? }
? ? ? ?// lists 處理完成后的其他業(yè)務(wù)操作一般代碼示例
public static void main(String[] args) throws InterruptedException {
? ? ? ? final CountDownLatch downLatch = new CountDownLatch(3);
? ? ? ? Await wait111 = new Await("wait111", downLatch);
? ? ? ? Await wait222 = new Await("wait222", downLatch);
? ? ? ? CountDownStart countDownStart = new CountDownStart(downLatch);
? ? ? ? wait111.start();
? ? ? ? wait222.start();
? ? ? ? Thread.sleep(1000);
? ? ? ? countDownStart.run();
? ? }
class Await extends Thread{
? ? private CountDownLatch countDownLatch;
? ? private String name;
? ? public Await(String name, CountDownLatch countDownLatch){
? ? ? ? this.name = name;
? ? ? ? this.countDownLatch = countDownLatch;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? System.out.println(name + " start.....");
? ? ? ? System.out.println(name + " run.....");
? ? ? ? try {
? ? ? ? ? ? countDownLatch.await();
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? System.out.println(name + " continue.....run");
? ? }
}
class CountDownStart extends Thread{
? ? private CountDownLatch countDownLatch;
? ? public CountDownStart(CountDownLatch countDownLatch){
? ? ? ? this.countDownLatch = countDownLatch;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? countDownLatch.countDown();
? ? ? ? countDownLatch.countDown();
? ? ? ? countDownLatch.countDown();
? ? ? ? System.out.println("start countDown");
? ? }
}運行結(jié)果:
wait222 start.....
wait222 run.....
wait111 start.....
wait111 run.....
start countDown
wait111 continue.....run
wait222 continue.....run
但是當我把線程等待去除后:
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? final CountDownLatch downLatch = new CountDownLatch(3);
? ? ? ? Await wait111 = new Await("wait111", downLatch);
? ? ? ? Await wait222 = new Await("wait222", downLatch);
? ? ? ? CountDownStart countDownStart = new CountDownStart(downLatch);
? ? ? ? wait111.start();
? ? ? ? wait222.start();
// ? ? ? ?Thread.sleep(1000);
? ? ? ? countDownStart.run();
? ? }結(jié)果:
start countDown
wait111 start.....
wait111 run.....
wait111 continue.....run
wait222 start.....
wait222 run.....
wait222 continue.....run
另外兩個線程線程并沒有開始就執(zhí)行,可能被搶占了,也可能調(diào)度優(yōu)先度不同,實際使用時還是需要多多實驗
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud OpenFeign 自定義響應(yīng)解碼器的問題記錄
我們在使用 Spring Cloud 微服務(wù)的時候,通常將返回結(jié)果使用一個JsonResult 類進行封裝,本文重點介紹SpringCloud OpenFeign 自定義響應(yīng)解碼器的問題記錄,感興趣的朋友跟隨小編一起看看吧2024-06-06
如何用Java實現(xiàn).env文件讀取敏感數(shù)據(jù)
這篇文章主要介紹了如何用Java實現(xiàn).env文件讀取敏感數(shù)據(jù),并提供了一個自動配置類EnvAutoConfiguration,common-env-starter-demo模塊展示了如何配置和啟動一個簡單的Spring Boot應(yīng)用程序,需要的朋友可以參考下2025-02-02
Maven配置項目依賴使用本地倉庫的方法匯總(小結(jié))
這篇文章主要介紹了Maven配置項目依賴使用本地倉庫的方法匯總(小結(jié)),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
淺談SpringMVC中Interceptor和Filter區(qū)別
這篇文章主要介紹了淺談SpringMVC中Interceptor和Filter區(qū)別,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04
SpringBoot2零基礎(chǔ)到精通之profile功能與自定義starter
SpringBoot是一種整合Spring技術(shù)棧的方式(或者說是框架),同時也是簡化Spring的一種快速開發(fā)的腳手架,本篇讓我們一起學(xué)習(xí)profile功能與自定義starter2022-03-03
詳解springboot接口如何優(yōu)雅的接收時間類型參數(shù)
這篇文章主要為大家詳細介紹了springboot的接口如何優(yōu)雅的接收時間類型參數(shù),文中為大家整理了三種常見的方法,希望對大家有一定的幫助2023-09-09

