Что делает CountDownLatch и как он помогает синхронизировать потоки
Что проверяют
Знаете ли вы механику и понимаете ли, что это одноразовый инструмент.
Как отвечать
Это синхронизатор с обратным счётчиком. Создаётся с начальным значением, потоки уменьшают его по завершении своей части, а ожидающий поток стоит на вызове ожидания, пока счётчик не дойдёт до нуля. После этого ожидание проходит мгновенно навсегда.
CountDownLatch latch = new CountDownLatch(3);
for (Task task : tasks) {
executor.submit(() -> {
try { task.run(); }
finally { latch.countDown(); } // обязательно в finally
});
}
latch.await(); // ждём, пока все три отметятсяДва типовых применения:
- дождаться завершения нескольких задач: счётчик равен их числу;
- запустить все потоки одновременно: счётчик равен единице, все рабочие ждут на нём, а управляющий поток отпускает их одним вызовом. Полезно в нагрузочных проверках, где нужен одновременный старт.
Главная ловушка, о которой надо сказать: уменьшение счётчика обязано выполняться в блоке гарантированного выполнения. Если задача упадёт с исключением до этого вызова, счётчик никогда не дойдёт до нуля, и ожидающий поток зависнет навсегда. По той же причине у ожидания есть версия с ограничением времени, и в рабочем коде обычно берут именно её.
Второе: счётчик одноразовый. Сбросить его нельзя. Нужен переиспользуемый барьер, где потоки ждут друг друга и после прохождения цикл повторяется, значит нужен другой синхронизатор.
Отличия от соседей: барьер циклический и симметричный, там все ждут всех; семафор раздаёт ограниченное число разрешений и возвращает их обратно.
В современном коде эту задачу часто решают иначе: композицией будущих результатов или структурированной конкурентностью с виртуальными потоками, где ожидание группы задач встроено.
Где спотыкаются
Не ставят уменьшение счётчика в блок гарантированного выполнения.
Ждут без ограничения времени и получают вечное зависание.
Пытаются переиспользовать счётчик повторно.
Путают его с барьером, где потоки ждут друг друга.