Можно ли запустить многопоточный алгоритм синхронно в Java?
Что проверяют
Различаете ли вы три понятия, которые часто сливают в одно: синхронный вызов, многопоточное выполнение и синхронизацию через synchronized. Интервьюер ждёт, что вы покажете способы дождаться результата параллельного алгоритма. Следом спрашивают, как такой алгоритм тестировать детерминированно.
Как отвечать
Да, и это обычная ситуация. «Синхронно» значит, что вызывающий код ждёт результат и продолжает только после него. Внутри может работать сколько угодно потоков. Способы:
parallelStream(): снаружи обычный блокирующий вызов, внутри задачи уходят вForkJoinPool.commonPool(), а вызывающий поток сам участвует в работе;ForkJoinPool.invoke(task): для рекурсивного «разделяй и властвуй» наRecursiveTask, возвращает результат, когда посчитано всё дерево;ExecutorService.invokeAll(tasks): блокируется, пока все задачи не завершатся, и возвращает списокFuture;CompletableFuture.allOf(...).join(): то же для цепочек;- низкоуровнево:
Thread.join()илиCountDownLatch.await(); StructuredTaskScope: структурная конкурентность, где подзадачи не переживают метод, в котором их создали. API долго находится в статусе preview и менялся между версиями, так что сверяйтесь со своей JDK.
Второй смысл вопроса: можно ли выполнить такой алгоритм в одном потоке, например для отладки или тестов. Можно, если алгоритм принимает Executor снаружи, а не создаёт пул сам. В тесте передаёте Runnable::run, и все задачи выполняются прямо в вызывающем потоке по порядку. Результат должен совпасть с параллельным прогоном, иначе в алгоритме гонка.
Где спотыкаются
Отвечают про synchronized и ReentrantLock. Это взаимное исключение на участке кода, к вопросу «синхронно или асинхронно» оно отношения не имеет.
Запускают алгоритм, где задачи ждут друг друга (через latch или get() у соседней задачи), на одном потоке или маленьком пуле. Задача занимает единственный поток и ждёт ту, которой не на чем выполниться: получается deadlock.
Кладут в parallelStream() блокирующие вызовы в сеть или базу. Общий пул маленький, по умолчанию число ядер минус один, и все parallel streams приложения начинают ждать друг друга.