什麼時候使用CountDownLatch

正如每個Java文檔所描述的那樣,CountDownLatch是一個同步工具類,它允許一個或多個線程一直等待,直到其他線程的操作執行完後再執行。在Java併發中,countdownlatch的概念是一個常見的面試題,所以一定要確保你很好的理解了它。在這篇文章中,我將會涉及到在Java併發編 程中跟CountDownLatch相關的以下幾點:

目錄

  • CountDownLatch是什麼?
  • CountDownLatch如何工作?
  • 在實時系統中的應用場景
  • 應用範例
  • 常見的面試題

CountDownLatch是什麼

CountDownLatch是在java1.5被引入的,跟它一起被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作後再執行。例如,應用程序的主線程希望在負責啟動框架服務的線程已經啟動所有的框架服務之後再執行。

CountDownLatch是通過一個計數器來實現的,計數器的初始值為線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。

什麼時候使用CountDownLatch

CountDownLatch的偽代碼如下所示:

//Main thread start

//Create CountDownLatch for N threads

//Create and start N threads

//Main thread wait on latch

//N threads completes there tasks are returns

//Main thread resume execution

CountDownLatch如何工作

CountDownLatch.java類中定義的構造函數:

//Constructs a CountDownLatch initialized with the given count.

public void CountDownLatch(int count) {...}

構造器中的計數值(count)實際上就是閉鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownLatch沒有提供任何機制去重新設置這個計數值

與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啟動其他線程後立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。

其他N 個線程必須引用閉鎖對象,因為他們需要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是通過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調 用了這個方法,count的值等於0,然後主線程就能通過await()方法,恢復執行自己的任務。

在實時系統中的使用場景

讓我們嘗試羅列出在java實時系統中CountDownLatch都有哪些使用場景。我所羅列的都是我所能想到的。如果你有別的可能的使用方法,請在留言裡列出來,這樣會幫助到大家。

  1. 實現最大的並行性:有時我們想同時啟動多個線程,實現最大程度的並行性。例如,我們想測試一個單例類。如果我們創建一個初始計數為1的CountDownLatch,並讓所有線程都在這個鎖上等待,那麼我們可以很輕鬆地完成測試。我們只需調用 一次countDown()方法就可以讓所有的等待線程同時恢復執行。
  2. 開始執行前等待n個線程完成各自任務:例如應用程序啟動類要確保在處理用戶請求前,所有N個外部系統已經啟動和運行了。
  3. 死鎖檢測:一個非常方便的使用場景是,你可以使用n個線程訪問共享資源,在每次測試階段的線程數目是不同的,並嘗試產生死鎖。

CountDownLatch使用例子

在這個例子中,我模擬了一個應用程序啟動類,它開始時啟動了n個線程類,這些線程將檢查外部系統並通知閉鎖,並且啟動類一直在閉鎖上等待著。一旦驗證和檢查了所有外部服務,那麼啟動類恢復執行。

BaseHealthChecker.java:這個類是一個Runnable,負責所有特定的外部服務健康的檢測。它刪除了重複的代碼和閉鎖的中心控制代碼。

public abstract class BaseHealthChecker implements Runnable {

private CountDownLatch _latch;

private String _serviceName;

private boolean _serviceUp;

//Get latch object in constructor so that after completing the task, thread can countDown() the latch

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;

}

//This methos needs to be implemented by all specific service checker

public abstract void verifyService();

}

NetworkHealthChecker.java:這個類繼承了BaseHealthChecker,實現了verifyService()方法。DatabaseHealthChecker.javaCacheHealthChecker.java除了服務名和休眠時間外,與NetworkHealthChecker.java是一樣的。

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");

}

}

ApplicationStartupUtil.java:這個類是一個主啟動類,它負責初始化閉鎖,然後等待,直到所有服務都被檢測完。

public class ApplicationStartupUtil

{

//List of service checkers

private static List _services;

//This latch will be used to wait on

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

{

//Initialize the latch with number of service checkers

_latch = new CountDownLatch(3);

//All add checker in lists

_services = new ArrayList();

_services.add(new NetworkHealthChecker(_latch));

_services.add(new CacheHealthChecker(_latch));

_services.add(new DatabaseHealthChecker(_latch));

//Start service checkers using executor framework

Executor executor = Executors.newFixedThreadPool(_services.size());

for(final BaseHealthChecker v : _services)

{

executor.execute(v);

}

//Now wait till all services are checked

_latch.await();

//Services are file and now proceed startup

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);

}

}

Output in console:

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

常見面試題

可以為你的下次面試準備以下一些CountDownLatch相關的問題:

  • 解釋一下CountDownLatch概念?
  • CountDownLatch 和CyclicBarrier的不同之處?
  • 給出一些CountDownLatch使用的例子?
  • CountDownLatch 類中主要的方法?


分享到:


相關文章: