Java CountDownLatch с примером

Согласно документации Java, CountDownLatch — это вспомогательное средство синхронизации, которое позволяет одному или нескольким потокам ожидать завершения набора операций, выполняемых в других потоках. Концепция CountDownLatch — очень распространенный вопрос на собеседованиипо параллелизму Java, поэтому убедитесь, что вы хорошо ее понимаете. В этой статье я расскажу о следующих моментах, связанных с CountDownLatch в параллелизме Java.

1. Класс CountDownLatch

CountDownLatch был представлен в JDK 1.5 вместе с другими параллельными утилитами, такими как CyclicBarrier, Semaphore, ConcurrentHashMap и BlockingQueue в пакете java.util.concurrent. Этот класс позволяет потоку Java ждать, пока другой набор потоков не завершит свои задачи.

Например, основной поток приложения хочет дождаться, пока другие потоки служб, отвечающие за запуск служб фреймворка, завершат свои задачи.

CountDownLatch работает, имея счетчик, инициализированный числом потоков, который уменьшается каждый раз, когда поток завершает свое выполнение. Когда счетчик достигает нуля, это означает, что все потоки завершили свое выполнение, и основной поток, ожидающий защелки, возобновляет выполнение.

CountdownLatch
Концепция CountDownLatch

Псевдокод для CountDownLatch можно записать следующим образом:

- Main thread start- Create CountDownLatch for N threads- Create and start N threads- Main thread wait on latch- N threads completes their tasks and count down the latch- Main thread resume execution

2. Как работает CountDownLatch?

Класс CountDownLatch определяет внутри один конструктор:

//Constructs a CountDownLatch initialized with the given count.public CountDownLatch(int count) {...}

Этот счетчик по сути является числом потоков, которые должна ждать защелка. Это значение может быть установлено только один раз, и оно не предоставляет никакого другого механизма для сброса этого счетчика.

  • Первое взаимодействие с CountDownLatch происходит в основном потоке, который будет ждать другие потоки. Этот основной поток должен вызвать метод CountDownLatch.await() сразу после запуска других потоков.
  • Выполнение будет остановлено на методе latch.await() до тех пор, пока другие потоки не завершат свое выполнение.
  • Обратите внимание, что эти N потоков должны иметь ссылку на объект защелки, поскольку им нужно будет уведомить объект защелки о том, что они завершили свою задачу. Это уведомление выполняется CountDownLatch.countDown().
  • Каждый вызов countDown() уменьшает начальное количество, установленное в конструкторе, на 1. Таким образом, когда все N потоков вызовут этот метод, количество достигнет нуля, и основному потоку будет разрешено возобновить выполнение после метода await().

3. Пример CountDownLatch

В этом примере я смоделировал класс запуска приложения, который запускает N потоков, которые будут проверять внешние системы и сообщать обратно защелке, на которой класс запуска ожидает. Как только все службы проверены и проверены, запуск продолжается.

BaseHealthChecker – Этот класс является 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 latchpublic BaseHealthChecker(String serviceName, CountDownLatch latch){super();this._latch = latch;this._serviceName = serviceName;this._serviceUp = false;}@Overridepublic 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 checkerpublic abstract void verifyService();}

NetworkHealthChecker: Этот класс расширяет BaseHealthChecker и должен обеспечить реализацию метода verifyService(). Аналогично, DatabaseHealthChecker и CacheHealthChecker идентичны NetworkHealthChecker, за исключением имен служб и времени сна.

public class NetworkHealthChecker extends BaseHealthChecker{public NetworkHealthChecker(CountDownLatch latch) {super("Network Service", latch);}@Overridepublic 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: этот класс является основным классом запуска, который инициализирует защелку и ждет ее, пока не будут проверены все службы.

public class ApplicationStartupUtil{//List of service checkersprivate static List<BaseHealthChecker> _services;//This latch will be used to wait onprivate 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<BaseHealthChecker>();_services.add(new NetworkHealthChecker(_latch));_services.add(new CacheHealthChecker(_latch));_services.add(new DatabaseHealthChecker(_latch));//Start service checkers using executor frameworkExecutor 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 startupfor(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);}}

Вывод программы:

Checking Network ServiceChecking Cache ServiceChecking Database ServiceDatabase Service is UPCache Service is UPNetwork Service is UPExternal services validation completed !! Result was :: true

4. Возможные варианты использования CountDownLatch

Давайте попробуем определить некоторые возможные варианты использования CountDownLatch в реальных приложениях Java. Я перечисляю, насколько я могу вспомнить. Если у вас есть другие возможные варианты использования, пожалуйста, оставьте комментарий. Это поможет другим.

  1. Достижение максимального параллелизма: Иногда мы хотим запустить несколько потоков одновременно, чтобы достичь максимального параллелизма. Например, мы хотим проверить класс на то, является ли он синглтоном. Это можно легко сделать, если мы создадим защелку с начальным числом 1 и заставим все потоки ждать защелки. Один вызов метода countDown() возобновит выполнение для всех ожидающих потоков одновременно.
  2. Дождитесь завершения N потоков, прежде чем возобновить выполнение. Например, класс запуска приложения хочет убедиться, что все N внешних систем находятся в рабочем состоянии и работают, прежде чем обрабатывать запросы пользователя.
  3. Обнаружение взаимоблокировки: очень удобный вариант использования, в котором можно использовать N потоков для доступа к общему ресурсу с разным количеством потоков на каждой фазе теста и попытаться создать взаимоблокировку.

5. Заключение

В этом уроке мы изучили основы CountDownLatch и как использовать его в реальных приложениях. Мы изучили важные методы и как использовать их для управления потоком приложения.

Прокрутить вверх