CountDownLatch.countDown(): примеры (JAVA)
CountDownLatch.countDown(): voidОбщее описание метода
Метод CountDownLatch.countDown() из пакета java.util.concurrent уменьшает внутренний счетчик экземпляра CountDownLatch на единицу. Первоначальный счетчик задается в конструкторе new CountDownLatch(count). Когда счетчик достигает нуля, все потоки, ожидающие вызова await() на этом же объекте, пробуждаются.
Ключевые свойства:
- Аргументы: отсутствуют.
- Возвращаемое значение:
void. - Идемпотентность при достижении нуля: дополнительные вызовы countDown() после достижения нуля не приводят к отрицательному значению счетчика и не бросают исключений.
- Блокирующее поведение: сам countDown() не блокирует вызывающий поток. Блокировка реализуется методами
await()иawait(long, TimeUnit). - Потокобезопасность: метод реализован для конкурентного использования несколькими потоками.
- Гарантии видимости: когда счетчик переходит в ноль, происходит эффект happens-before для операций до countDown() и операций после успешного
await().
Типичные сценарии применения: синхронизация завершения множества задач перед дальнейшей обработкой, реализация стартового сигнала для группы потоков (счетчик=1), ожидание инициализации ресурсов перед продолжением работы.
Простые варианты использования
Ниже приведены короткие примеры. В каждом примере показан код и возможный вывод.
1) Базовый пример: главный поток ждет завершения двух воркеров.
import java.util.concurrent.CountDownLatch;
public class SimpleExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
System.out.println("Worker A started");
// имитация работы
try { Thread.sleep(200); } catch (InterruptedException ignored) {}
latch.countDown();
System.out.println("Worker A finished");
}).start();
new Thread(() -> {
System.out.println("Worker B started");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
latch.countDown();
System.out.println("Worker B finished");
}).start();
latch.await();
System.out.println("All workers completed");
}
}
Возможный вывод: Worker A started Worker B started Worker B finished Worker A finished All workers completed
2) Вызов countDown больше раз, чем исходный счетчик (без ошибок).
import java.util.concurrent.CountDownLatch;
public class ExtraCountDown {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(1);
latch.countDown(); // счетчик становится 0
latch.countDown(); // дополнительных ошибок не будет
System.out.println("After extra countDown");
}
}
Вывод: After extra countDown
3) await с таймаутом показывает, что countDown может не успеть.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TimeoutExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
// только один воркер
new Thread(() -> {
try { Thread.sleep(300); } catch (InterruptedException ignored) {}
latch.countDown();
}).start();
boolean finished = latch.await(200, TimeUnit.MILLISECONDS);
System.out.println("Await finished: " + finished);
}
}
Вывод: Await finished: false
Аналоги в Java и их отличия
- Phaser: более гибкий инструмент для многоступенчатой синхронизации и повторного использования. Предпочтительнее, когда требуется несколько фаз или динамическое изменение числа участников.
- CyclicBarrier: синхронизирует набор потоков на одной фазе и автоматически сбрасывается для повторного использования. Подходит, когда участники должны синхронизироваться много раз.
- Semaphore: управляет количеством одновременных доступов, а не ожиданием завершения фиксированного числа задач. Используется для ограничения параллелизма.
- CompletableFuture: предоставляет декларативный стиль композиции асинхронных задач, удобен для независимых задач с обработкой результата и цепочками обработки.
- CountDownLatch предпочтительно, когда требуется простое одноразовое ожидание завершения заранее известного числа событий.
Соответствия в других языках
Краткие эквиваленты и отличия по языкам с примерами.
- Go:
sync.WaitGroup- прямой аналог. Пример:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
fmt.Println("Worker 1 done")
}()
go func() {
defer wg.Done()
fmt.Println("Worker 2 done")
}()
wg.Wait()
fmt.Println("All completed")
}
Возможный вывод: Worker 1 done Worker 2 done All completed
- C#:
System.Threading.CountdownEvent- поведение очень похоже.
using System;
using System.Threading;
class Program {
static void Main() {
var latch = new CountdownEvent(2);
new Thread(() => { Thread.Sleep(100); latch.Signal(); Console.WriteLine("A"); }).Start();
new Thread(() => { Thread.Sleep(200); latch.Signal(); Console.WriteLine("B"); }).Start();
latch.Wait();
Console.WriteLine("Done");
}
}
Возможный вывод: A B Done
- Python: в стандартной библиотеке прямого аналога нет, используются
threading.Barrier,threading.Eventили сочетание счетчика с блокировкой. В asyncio используетсяasyncio.gatherдля ожидания набора задач.
# Пример с threading.Event как упрощенный сигнал
import threading
latch_event = threading.Event()
def worker():
print('Worker started')
latch_event.set() # не эквивалент точный, показан сигнал
threading.Thread(target=worker).start()
# ожидание сигнала
latch_event.wait()
print('Notified')
Возможный вывод: Worker started Notified
- JavaScript: вместо блокировок часто используются
Promise.allдля ожидания множества асинхронных операций.
// Node.js / браузер
Promise.all([
new Promise(res => setTimeout(() => res('A'), 100)),
new Promise(res => setTimeout(() => res('B'), 200))
]).then(results => console.log('All:', results));
Вывод через ~200ms: All: [ 'A', 'B' ]
- PHP: нет встроенной структуры; в расширениях для параллелизма (pthreads, parallel) применяются другие примитивы, часто используется синхронизация через каналы или семафоры.
- Kotlin: на JVM использует те же примитивы, но для корутин предпочитаются
DeferredиawaitAllиз kotlinx.coroutines. - Lua: в стандартной библиотеке не предусмотрено, в средах с потоками или корутинами применяются каналы или внешние библиотеки.
Отличие от Java: в некоторых языках модель асинхронности не блокирующая (Promise/async), поэтому ожидание завершения реализуется через композицию колбэков или обещаний, а не через блокирующие примитивы.
Типичные ошибки и их проявления
- Неправильный начальный счетчик. Если указать слишком большое значение, ожидание никогда не закончится. Пример:
CountDownLatch latch = new CountDownLatch(3);
// Запущены только 2 задачи, забыта третья signal
// latch.await(); // будет блокировать навсегда
Результат: главный поток может блокироваться бесконечно.
- Ожидание повторного использования. CountDownLatch одноразовая конструкция и не сбрасывается автоматически. Частая ошибка - ожидание, что latch можно переиспользовать для следующей фазы.
- Вызов
countDown()не в блоке finally. При выбросе исключения в задаче countDown может не выполниться и привести к зависанию ожидающих потоков. Рекомендуется размещатьcountDown()в finally:
try {
// работа
} finally {
latch.countDown();
}
Если убрать finally и случится исключение, ожидание может не завершиться.
- Игнорирование InterruptedException у
await(). При прерывании необходимо обрабатывать состояние потока и, при необходимости, корректно завершать работу. - Ожидание в UI-потоке. Блокировка основного потока интерфейса приведет к зависанию приложения.
История и изменения API
Класс CountDownLatch введен в Java 5 как часть пакета java.util.concurrent. Сам метод countDown() и семантика счётчика оставались стабильными в последующих версиях Java; существенных изменений в поведении или сигнатуре метода не было. При переходе к современным асинхронным подходам (CompletableFuture, реактивные библиотеки) появились альтернативные паттерны, но сам примитив сохраняет прежнюю роль для простых сценариев синхронизации.
Расширенные и редкие сценарии применения
Несколько более сложных примеров с пояснениями.
1) Использование как стартового шлюза (start gate) для одновременного старта множества воркеров.
import java.util.concurrent.CountDownLatch;
public class StartGateExample {
public static void main(String[] args) throws InterruptedException {
int workers = 3;
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(workers);
for (int i = 0; i < workers; i++) {
final int id = i;
new Thread(() -> {
try {
startGate.await(); // ждет общего сигнала
System.out.println("Worker " + id + " running");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endGate.countDown();
}
}).start();
}
// подготовка
Thread.sleep(100);
System.out.println("Releasing workers");
startGate.countDown(); // все воркеры стартуют почти одновременно
endGate.await();
System.out.println("All workers done");
}
}
Возможный вывод: Releasing workers Worker 0 running Worker 1 running Worker 2 running All workers done
Комментарий: сочетание двух latch (startGate и endGate) дает контролируемый старт и надежный финиш.
2) Гарантия выполнения countDown в finally для избежания дедлоков и корректного учета завершений.
// В фрагменте worker'а
try {
// работа, может бросать исключения
} finally {
latch.countDown(); // всегда уменьшается счетчик
}
Результат: даже при исключении главный поток не будет ждать навсегда.
3) Эмулирование повторного использования: создание нового CountDownLatch для каждой итерации или использование Phaser, если перезапуск после каждой фазы обязателен.
// Псевдокод для цикла фаз
for (int phase = 0; phase < N; phase++) {
CountDownLatch latch = new CountDownLatch(tasks);
// запустить задачи, которые вызовут latch.countDown()
latch.await();
}
Результат: каждая фаза использует новый latch и завершение каждой фазы корректно отслеживается.
4) Сбор частичных результатов с таймаутом: выявление сколько задач успели завершиться.
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class PartialResults {
public static void main(String[] args) throws InterruptedException {
int tasks = 5;
CountDownLatch latch = new CountDownLatch(tasks);
AtomicInteger completed = new AtomicInteger(0);
ExecutorService es = Executors.newFixedThreadPool(tasks);
for (int i = 0; i < tasks; i++) {
es.submit(() -> {
try {
// случайная длительность
Thread.sleep((long)(Math.random() * 400));
completed.incrementAndGet();
} catch (InterruptedException ignored) {}
finally {
latch.countDown();
}
});
}
boolean allDone = latch.await(250, TimeUnit.MILLISECONDS);
System.out.println("Completed tasks: " + completed.get());
System.out.println("All finished within timeout: " + allDone);
es.shutdownNow();
}
}
Возможный вывод: Completed tasks: 3 All finished within timeout: false
Комментарий: комбинация счетчика и атомарного счётчика позволяет узнать, сколько задач завершено к моменту таймаута.
5) Взаимодействие с CompletableFuture: запуск нескольких futures и ожидание их завершения без прямого использования CountDownLatch - демонстрирует альтернативный стиль.
import java.util.concurrent.*;
public class FuturesExample {
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newFixedThreadPool(2);
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "A", es);
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "B", es);
CompletableFuture.allOf(f1, f2).join();
System.out.println("Results: " + f1.get() + ", " + f2.get());
es.shutdown();
}
}
Вывод: Results: A, B
Комментарий: для чисто асинхронных сценариев такой подход часто удобнее, чем ручная синхронизация.
джава CountDownLatch.countDown() function comments
- джава CountDownLatch.countDown() - аргументы и возвращаемое значение
- Функция java CountDownLatch.countDown() - описание
- CountDownLatch.countDown() - примеры
- CountDownLatch.countDown() - похожие методы на java
- CountDownLatch.countDown() на javascript, c#, python, php
- CountDownLatch.countDown() изменения java
- Примеры CountDownLatch.countDown() на джава