CountDownLatch

CountDownLatch介紹

CountDownLatch是一個同步工具類,它允許一個或多個線程一直等待偎肃,直到其他線程執(zhí)行完后再執(zhí)行。例如滞详,應(yīng)用程序的主線程希望在負(fù)責(zé)啟動框架服務(wù)的線程已經(jīng)啟動所有框架服務(wù)之后執(zhí)行紊馏。

CountDownLatch原理

CountDownLatch是通過一個計數(shù)器來實現(xiàn)的,計數(shù)器的初始化值為線程的數(shù)量稀火。每當(dāng)一個線程完成了自己的任務(wù)后赌朋,計數(shù)器的值就相應(yīng)得減1。當(dāng)計數(shù)器到達0時赡若,表示所有的線程都已完成任務(wù)团甲,然后在閉鎖上等待的線程就可以恢復(fù)執(zhí)行任務(wù)。

CountDownLatch原理示意圖

CountDownLatch的偽代碼

Main thread start
Create CountDownLatch for N threads
Create and start N threads
Main thead wait on latch
N threads completes there tasks are returns
Main thread resume execution

CountDownLatch.java中定義的構(gòu)造函數(shù)

//用等待的線程數(shù)量來進行初始化
public void CountDownLatch(int count){...}

計數(shù)器count是閉鎖需要等待的線程數(shù)量身腻,只能被設(shè)置一次,且CountDownLatch沒有提供任何機制去重新設(shè)置計數(shù)器count脐区。

與CountDownLatch的第一次交互是主線程等待其他線程她按。主線程必須在啟動其他線程后立即調(diào)用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞媒佣,直到其他線程完成各自的任務(wù)陵刹。

其他N個線程必須引用CountDownLatch閉鎖對象,因為它們需要通知CountDownLatch對象授霸,它們各自完成了任務(wù)碘耳;這種通知機制是通過CountDownLatch.countDown()方法來完成的框弛;每調(diào)用一次,count的值就減1瑟枫,因此當(dāng)N個線程都調(diào)用這個方法慷妙,count的值就等于0,然后主線程就可以通過await()方法膝擂,恢復(fù)執(zhí)行自己的任務(wù)。

在實時系統(tǒng)中的使用場景

  1. 實現(xiàn)最大的并行性:有時我們想同時啟動多個線程狞山,實現(xiàn)最大程度的并行性叉寂。例如,我們想測試一個單例類勘纯。如果我們創(chuàng)建一個初始計數(shù)器為1的CountDownLatch,并讓其他所有線程都在這個鎖上等待腌逢,只需要調(diào)用一次countDown()方法就可以讓其他所有等待的線程同時恢復(fù)執(zhí)行超埋。
  2. 開始執(zhí)行前等待N個線程完成各自任務(wù):例如應(yīng)用程序啟動類要確保在處理用戶請求前,所有N個外部系統(tǒng)都已經(jīng)啟動和運行了媒惕。
  3. 死鎖檢測:一個非常方便的使用場景是你用N個線程去訪問共享資源来庭,在每個測試階段線程數(shù)量不同,并嘗試產(chǎn)生死鎖月弛。

CountDownLatch使用例子

模擬一個應(yīng)用程序啟動類帽衙,開始就啟動N個線程,去檢查N個外部服務(wù)是否正常并通知閉鎖厉萝;啟動類一直在閉鎖上等待,一旦驗證和檢查了所有外部服務(wù)章母,就恢復(fù)啟動類執(zhí)行翩剪。

BaseHealthChecker.java :這個類是實現(xiàn)了Runnable接口,負(fù)責(zé)所有特定的外部服務(wù)健康檢查的基類舞肆。

import java.util.concurrent.CountDownLatch;

public abstract class BaseHealthChecker implements Runnable {
    
    private CountDownLatch _latch;
    private String _serviceName;
    private boolean _serviceUp;
    
    public BaseHealthChecker(String serviceName, CountDownLatch latch)
    {
        super();
        this._latch = latch;
        this._serviceName = serviceName;
        this._serviceUp = false;
    }

    @Override
    public void run() {
        try {
            verifyService();
            _serviceUp = true;
        } catch (Throwable t) {
            t.printStackTrace(System.err);
            _serviceUp = false;
        } finally {
            if(_latch != null) {
                _latch.countDown();
            }
        }
    }

    public String getServiceName() {
        return _serviceName;
    }

    public boolean isServiceUp() {
        return _serviceUp;
    }
    
    public abstract void verifyService();
}

NetworkHealthChecker.java,DatabaseHealthChecker.java和CacheHealthChecker.java都繼承自BaseHealthChecker椿胯,引用CountDownLatch實例剃根,除了服務(wù)名和休眠時間不同外,都實現(xiàn)各自的verifyService方法廉油。

NetworkHealthChecker.java類

import java.util.concurrent.CountDownLatch;

public class NetworkHealthChecker extends BaseHealthChecker
{
    public NetworkHealthChecker (CountDownLatch latch)
    {
        super("Network Service", latch);
    }
    
    @Override
    public void verifyService() 
    {
        System.out.println("Checking " + this.getServiceName());
        try 
        {
            Thread.sleep(7000);
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(this.getServiceName() + " is UP");
    }
}

DatabaseHealthChecker.java類

import java.util.concurrent.CountDownLatch;

public class DatabaseHealthChecker extends BaseHealthChecker
{
    public DatabaseHealthChecker (CountDownLatch latch)
    {
        super("Database Service", latch);
    }
    
    @Override
    public void verifyService() 
    {
        System.out.println("Checking " + this.getServiceName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getServiceName() + " is UP");
    }
}

CacheHealthChecker.java類

import java.util.concurrent.CountDownLatch;

public class CacheHealthChecker extends BaseHealthChecker
{
    public CacheHealthChecker (CountDownLatch latch)
    {
        super("Cache Service", latch);
    }
    
    @Override
    public void verifyService() 
    {
        System.out.println("Checking " + this.getServiceName());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getServiceName() + " is UP");
    }
}

ApplicationStartupUtil.java:是一個主啟動類抒线,它負(fù)責(zé)初始化閉鎖,然后等待所有服務(wù)都被檢查完成嘶炭,再恢復(fù)執(zhí)行。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class ApplicationStartupUtil 
{
    private static List<BaseHealthChecker> _services;
    private static CountDownLatch _latch;
    
    private ApplicationStartupUtil()
    {
    }
    
    private final static ApplicationStartupUtil INSTANCE = new ApplicationStartupUtil();
    
    public static ApplicationStartupUtil getInstance()
    {
        return INSTANCE;
    }
    
    public static boolean checkExternalServices() throws Exception
    {
        _latch = new CountDownLatch(3);
        _services = new ArrayList<BaseHealthChecker>();
        _services.add(new NetworkHealthChecker(_latch));
        _services.add(new CacheHealthChecker(_latch));
        _services.add(new DatabaseHealthChecker(_latch));
        
        Executor executor = Executors.newFixedThreadPool(_services.size());
        
        for(final BaseHealthChecker v : _services) 
        {
            executor.execute(v);
        }
        
        _latch.await();
        
        for(final BaseHealthChecker v : _services) 
        {
            if( ! v.isServiceUp())
            {
                return false;
            }
        }
        return true;
    }
}

測試代碼檢測閉鎖功能:

public class Main {
    public static void main(String[] args) 
    {
        boolean result = false;
        try {
            result = ApplicationStartupUtil.checkExternalServices();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("External services validation completed !! Result was :: "+ result);
    }
}

執(zhí)行結(jié)果:

Checking Network Service
Checking Cache Service
Checking Database Service
Database Service is UP
Cache Service is UP
Network Service is UP
External services validation completed !! Result was :: true

工作中的使用(優(yōu)雅地完成初始化)

在移動應(yīng)用開發(fā)中(以Android為例),隨著功能的增多寺渗,應(yīng)用初始化工作開始增多兰迫,網(wǎng)絡(luò),賬號涡拘,推送服務(wù)须鼎,預(yù)加載數(shù)據(jù)等依次登場府蔗,開發(fā)人員都會臨時在Application中找到現(xiàn)有初始化邏輯,將自己的代碼插在其中姓赤。隨著版本的迭代,新老員工的交替蝌焚,幾乎沒人能對應(yīng)用的初始化過程完全了解誓斥,刪除一行初始化代碼甚至移動位置都可能造成嚴(yán)重的后果。

應(yīng)用初始化過程極其重要毕谴,它是應(yīng)用后續(xù)平穩(wěn)運行的前提和保證。開發(fā)初始化配置模塊(公司內(nèi)部開源不宜公開)循帐,更好地管理初始化邏輯舀武,對初始化地工作進行分層,分優(yōu)先級银舱,多線程地規(guī)劃,進而在大幅提升初始化效率柿顶,同時還有完整地日志監(jiān)控體系功能操软。有了它,規(guī)劃整個初始化工作將簡單而優(yōu)雅

參考

  1. 什么時候使用CountDownLatch
  2. Java concurrency – CountDownLatch Example
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末家乘,一起剝皮案震驚了整個濱河市藏澳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌业崖,老刑警劉巖蓄愁,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妇斤,居然都是意外死亡丹拯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進店門死相,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咬像,“玉大人,你說我怎么就攤上這事钮惠。” “怎么了蔑赘?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵预明,是天一觀的道長。 經(jīng)常有香客問我酥馍,道長阅酪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任砚尽,我火速辦了婚禮辉词,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瑞躺。我一直安慰自己幢哨,他們只是感情好赡勘,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布狮含。 她就那樣靜靜地躺著曼振,像睡著了一般蔚龙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甲雅,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天,我揣著相機與錄音弛姜,去河邊找鬼妖枚。 笑死,一個胖子當(dāng)著我的面吹牛绝页,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播莱没,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼饰躲,長吁一口氣:“原來是場噩夢啊……” “哼臼隔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躬翁,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤盒发,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宁舰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡腋腮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年即寡,在試婚紗的時候發(fā)現(xiàn)自己被綠了袜刷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡墩蔓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昏名,到底是詐尸還是另有隱情阵面,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布嗽交,位于F島的核電站颂斜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沃疮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一邑茄、第九天 我趴在偏房一處隱蔽的房頂上張望俊啼。 院中可真熱鬧,春花似錦同木、人聲如沸跛十。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奈偏。三九已至,卻和暖如春霎苗,著一層夾襖步出監(jiān)牢的瞬間榛做,已是汗流浹背内狸。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工昆淡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刽严,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓舞萄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親撑螺。 傳聞我的和親對象是個殘疾皇子崎弃,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容