Как построить приложение с несколькими потоками в Java
Javaасинхронностьмногопоточностьпотокиразработка приложений
Что проверяют
Знаете ли вы современные средства вместо ручного управления потоками и понимаете ли, где возникают гонки.
Как отвечать
Ручное создание потоков сегодня почти не используется. Работа строится вокруг исполнителей:
try (var executor = Executors.newFixedThreadPool(8)) {
List<Future<Result>> futures = tasks.stream().map(t -> executor.submit(() -> process(t))).toList();
for (var f : futures) collect(f.get());
}Что стоит назвать:
- пул под задачу: фиксированный для вычислений (по числу ядер), отдельный для блокирующих операций, чтобы они не занимали общий. В новых версиях для ожидания берут виртуальные потоки, где поток на задачу перестал быть проблемой;
- композиция:
CompletableFutureдля цепочек и объединения результатов,ExecutorCompletionServiceдля обработки по мере готовности; - общее состояние: либо неизменяемые объекты, либо конкурентные коллекции (
ConcurrentHashMap,BlockingQueue), либо явная синхронизация. Простой счётчик этоAtomicLongилиLongAdder; - синхронизация:
synchronizedилиReentrantLock, с единым порядком захвата, чтобы не получить взаимную блокировку, и с минимальной областью, чтобы не убить параллелизм; - видимость: без
volatileили синхронизации один поток может не увидеть изменения другого. Это отдельная тема, которую спрашивают следом; - завершение: корректная остановка через прерывание и ожидание, а не принудительное убийство потока;
- проверка: гонки ловятся не глазами, а стресс-тестами и инструментами, потому что воспроизводятся редко.
Где спотыкаются
Создают потоки в цикле вместо пула и получают тысячи потоков под нагрузкой.
Синхронизируют весь метод, включая сетевой вызов, и превращают параллельный код в последовательный.
Считают, что volatile делает операцию атомарной: он про видимость, а не про неделимость.