CountDownLatch.await(): примеры (JAVA)
CountDownLatch.await(): voidОписание метода await()
Класс java.util.concurrent.CountDownLatch предоставляет механизм ожидания, основанный на счётчике. Метод await() блокирует вызывающий поток до тех пор, пока внутренний счётчик не достигнет нуля, либо пока не произойдёт прерывание.
Доступные сигнатуры:
public void await() throws InterruptedException- блокировка до достижения нуля или до прерывания; не возвращает значения.public boolean await(long timeout, java.util.concurrent.TimeUnit unit) throws InterruptedException- блокировка максимум указанное время; возвращаетtrue, если счётчик достиг нуля до истечения времени, иначеfalse. Также может быть прервано с генерированием InterruptedException.
Аргументы и возвращаемые значения:
- Аргумент
timeout- числовое значение лимита ожидания. - Аргумент
unit- перечислениеTimeUnit(NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS и т.д.). Передачаnullприведёт кNullPointerException. - Возвращаемое значение у тайм-аута - логическое:
trueпри достижении нуля до таймаута,falseпри превышении времени; версия без таймаута возвращает ничего (void) и завершает работу только при достижении нуля или прерывании.
Исключения и гарантии видимости:
- Оба метода бросают
InterruptedException, если поток был прерван во время ожидания. - Каждый вызов
countDown()happens-before завершению любого успешного вызоваawait(). Это обеспечивает видимость изменений, выполненных доcountDown(), в потоках, продолживших работу послеawait().
Особенности поведения:
- CountDownLatch не является цикличным: после достижения нуля его нельзя «сбросить» и повторно использовать. Для повторного использования подходят другие примитивы, например
CyclicBarrierилиPhaser. - Несколько потоков могут одновременно вызывать
await(); все они разблокируются, когда счётчик достигает нуля. - Если счётчик уже равен нулю, вызов
await()возвращает немедленно.
Короткие примеры использования
Пример 1: простой сценарий, где главный поток ждёт завершения двух рабочих потоков.
import java.util.concurrent.CountDownLatch;
public class Example1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
try { Thread.sleep(300); } catch (InterruptedException e) { }
System.out.println("worker1 done");
latch.countDown();
}).start();
new Thread(() -> {
try { Thread.sleep(200); } catch (InterruptedException e) { }
System.out.println("worker2 done");
latch.countDown();
}).start();
System.out.println("main waiting");
latch.await();
System.out.println("main continues");
}
}
main waiting worker2 done worker1 done main continues
Пример 2: версия с таймаутом, где ожидание истекает и возвращает false.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Example2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
System.out.println(latch.await(100, TimeUnit.MILLISECONDS)); // нет decrement, таймаут
}
}
false
Пример 3: прерывание ожидания вызывает InterruptedException.
import java.util.concurrent.CountDownLatch;
public class Example3 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Thread t = new Thread(() -> {
try {
latch.await();
System.out.println("unblocked");
} catch (InterruptedException e) {
System.out.println("was interrupted");
}
});
t.start();
Thread.sleep(100);
t.interrupt();
}
}
was interrupted
Аналоги внутри Java и их особенности
- CyclicBarrier - барьер для группы потоков, используется, когда требуется многократно синхронизировать набор потоков; возвращает участников на каждом цикле и повторно применим.
- Phaser - более гибкая и расширяемая синхронизация фаз; поддерживает динамическое добавление и удаление участников и многофазные схемы.
- Semaphore - ограничение количества одновременных ресурсов; не прямой аналог, но иногда применяется для ожидания освобождения ресурсов.
- CompletableFuture / Future - удобнее для асинхронных вычислений и композиции задач, особенно при работе с результатами; await-паттерн заменяется вызовами
get()или комбинациями методов CompletableFuture. - Thread.join() - простая альтернатива, если требуется дождаться конкретного потока; не подходит для ожидания множества анонимных задач в пуле без явных ссылок на потоки.
Выбор зависит от сценария: для одноразового ожидания нескольких событий подходит CountDownLatch; для многоразовой синхронизации - CyclicBarrier или Phaser; для работы с результатами и композицией удобнее CompletableFuture.
Аналоги в других языках и основные отличия
Краткие эквиваленты и подходы в популярных языках:
- Go:
sync.WaitGroup- наиболее близкий аналог. Пример:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
time.Sleep(200 * time.Millisecond)
fmt.Println("worker1 done")
wg.Done()
}()
go func() {
time.Sleep(100 * time.Millisecond)
fmt.Println("worker2 done")
wg.Done()
}()
fmt.Println("main waiting")
wg.Wait()
fmt.Println("main continues")
}
main waiting worker2 done worker1 done main continues
- C#:
System.Threading.CountdownEvent- функционально близок. Пример:
using System;
using System.Threading;
class Program {
static void Main() {
var latch = new CountdownEvent(2);
new Thread(() => { Thread.Sleep(200); Console.WriteLine("w1"); latch.Signal(); }).Start();
new Thread(() => { Thread.Sleep(100); Console.WriteLine("w2"); latch.Signal(); }).Start();
latch.Wait();
Console.WriteLine("main continues");
}
}
w2 w1 main continues
- Python: нет точного аналога в стандартной библиотеке, но есть
threading.Barrierиthreading.Event. Для ожидания завершения множества потоков часто применяется набор объектов Thread + join илиconcurrent.futures:
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=2) as ex:
futures = [ex.submit(lambda t: (print(f'w{i} done'), time.sleep(0.1))[0], i) for i in range(2)]
for f in futures:
f.result()
print('main continues')
w0 done w1 done main continues
- JavaScript (Node.js): паттерн с
Promise.allвыполняет роль ожидания множества асинхронных операций:
Promise.all([
new Promise(r => setTimeout(() => { console.log('w1'); r(); }, 200)),
new Promise(r => setTimeout(() => { console.log('w2'); r(); }, 100))
]).then(() => console.log('main continues'));
w2 w1 main continues
- Kotlin: используется тот же
java.util.concurrent.CountDownLatchили корутины сjoinиDeferred. - PHP, SQL, Lua: прямых встроенных аналогов нет. Применяются внешние расширения (Swoole, pthreads), очереди сообщений или управляющие механизмы на уровне процессов. Отличие: в большинстве сценариев синхронизация в этих языках реализуется на уровне событий или потоков ОС, а не стандартной библиотеки в одном стиле.
Вывод: в языках с поддержкой примитивов синхронизации чаще всего присутствует близкий эквивалент; в асинхронных средах роль CountDownLatch выполняет композиция промисов или событий.
Типичные ошибки при использовании await()
- Забывание вызовов
countDown(), приводящее к вечному ожиданию. Пример:
// Пример: main будет вечно ждать, так как countDown не вызывается
CountDownLatch latch = new CountDownLatch(1);
System.out.println("about to wait");
latch.await(); // блокировка навсегда
// (приложение зависает, никакого вывода после "about to wait")
- Игнорирование InterruptedException. Если исключение не обрабатывается, возможны некорректные состояния и потеря информации о прерывании. Пример неправильной обработки:
try {
latch.await();
} catch (InterruptedException ignored) {
// пустой catch - потеря сигнала прерывания
}
// программа игнорирует прерывание и продолжает без реакций
- Передача
nullв аргументTimeUnitв версии с таймаутом приводит кNullPointerException. Пример:
latch.await(100, null);
Exception in thread "main" java.lang.NullPointerException
at java.base/.../CountDownLatch.await(CountDownLatch.java:...)
- Использование CountDownLatch как многоразового барьера (ожидание-повтор), хотя он одноразовый. Частая ошибка - попытка переиспользовать тот же экземпляр после достижения нуля; повторные ожидания возвращают немедленно, но повторного увеличения счётчика не предусмотрено.
Изменения в методе за последние версии Java
CountDownLatch и его методы await() присутствуют в Java с версии 5 (JDK 1.5). За последующие релизы изменений сигнатур этих методов не происходило. Начиная с более новых версий платформы появились альтернативы в пакете java.util.concurrent (например, Phaser) и улучшения в производительности библиотеки в целом, но сам API CountDownLatch остался стабильным.
Расширенные и нетипичные примеры
Пример A: gate для одновременного старта рабочих потоков (start gate pattern).
import java.util.concurrent.CountDownLatch;
public class StartGate {
public static void main(String[] args) throws InterruptedException {
int n = 3;
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(n);
for (int i = 0; i < n; i++) {
final int id = i;
new Thread(() -> {
try {
startGate.await(); // ждём общего старта
System.out.println("worker " + id + " started");
Thread.sleep(100 + id * 50);
System.out.println("worker " + id + " done");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endGate.countDown();
}
}).start();
}
System.out.println("releasing start gate");
startGate.countDown(); // все рабочие стартуют почти одновременно
endGate.await();
System.out.println("all done");
}
}
releasing start gate worker 0 started worker 1 started worker 2 started worker 0 done worker 1 done worker 2 done all done
Комментарий: шаблон полезен для измерений и для синхронного старта задач.
Пример B: ожидание с таймаутом и fallback-логика.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TimedFallback {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
try {
Thread.sleep(500); // медленная инициализация
System.out.println("init done");
latch.countDown();
} catch (InterruptedException e) { }
}).start();
if (!latch.await(200, TimeUnit.MILLISECONDS)) {
System.out.println("fallback used");
} else {
System.out.println("proceed normally");
}
}
}
fallback used init done
Комментарий: полезно при ожидании холодного старта сервиса с ограничением времени.
Пример C: использование вместе с ExecutorService и обработка прерываний.
import java.util.concurrent.*;
public class ExecutorLatch {
public static void main(String[] args) throws InterruptedException {
int tasks = 5;
CountDownLatch latch = new CountDownLatch(tasks);
ExecutorService ex = Executors.newFixedThreadPool(3);
for (int i = 0; i < tasks; i++) {
ex.submit(() -> {
try {
// работа
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
// ожидание со временем и аккуратная остановка пула
if (!latch.await(1, TimeUnit.SECONDS)) {
System.out.println("timeout, shutdown now");
ex.shutdownNow();
} else {
System.out.println("all tasks finished");
ex.shutdown();
}
}
}
all tasks finished
Комментарий: сочетание таймаутов и корректного завершения пула даёт управляемую обработку долгих задач.
Пример D: использование нескольких лочек для построения конвейера (pipeline).
import java.util.concurrent.CountDownLatch;
public class Pipeline {
public static void main(String[] args) throws InterruptedException {
CountDownLatch stage1 = new CountDownLatch(1);
CountDownLatch stage2 = new CountDownLatch(1);
new Thread(() -> {
try {
// этап 1
System.out.println("stage1 work");
Thread.sleep(100);
stage1.countDown();
} catch (InterruptedException e) {}
}).start();
new Thread(() -> {
try {
stage1.await();
System.out.println("stage2 work");
Thread.sleep(100);
stage2.countDown();
} catch (InterruptedException e) {}
}).start();
stage2.await();
System.out.println("pipeline finished");
}
}
stage1 work stage2 work pipeline finished
Комментарий: несколько latch позволяют описать последовательные стадии без явных очередей.
Пример E: использование для тестирования асинхронного кода (ожидание события из теста).
// В тесте: создаётся CountDownLatch(1), запускается асинхронная операция, в колбэке вызывается countDown(),
// тест ждёт с разумным таймаутом и проверяет результат.
// Ожидаемый результат теста: успешное завершение до таймаута либо явный провал с сообщением о таймауте
джава CountDownLatch.await() function comments
- джава CountDownLatch.await() - аргументы и возвращаемое значение
- Функция java CountDownLatch.await() - описание
- CountDownLatch.await() - примеры
- CountDownLatch.await() - похожие методы на java
- CountDownLatch.await() на javascript, c#, python, php
- CountDownLatch.await() изменения java
- Примеры CountDownLatch.await() на джава