Как узнать, когда все потоки в ExecutorService завершены?
Я знаю это shutdown()
а также awaitTermination()
существовать. Проблема заключается в том, что runnables в пуле должны иметь возможность добавить к нему неизвестный номер (не может использовать обратный отсчет) других runnables, и если я позвоню shutdown()
эти задачи будут отклонены. Как я могу знать, когда они закончили?
3 ответа
Вместо отправки Runnable
задачи к Executor
Лучше использовать ForkJoinTask
/ ForkJoinPool
вместо. ForkJoinTask
работает внутри ForkJoinPool
и может порождать произвольное количество (под) задач и ждать их завершения, фактически не блокируя текущий поток. ForkJoinTask
завершается, когда все его подзадачи выполнены, поэтому все вычисления выполняются, когда начальный (root) ForkJoinTask
завершено.
Смотрите Oracle - Учебные руководства Java™ - Fork/Join для деталей.
Поскольку все ваши задачи безрезультатны (Runnable
), вы должны подкласс RecursiveAction
(который сам по себе является подклассом ForkJoinTask
). Реализуйте метод compute()
и порождают там произвольное количество новых задач, либо вызывая invoke(subtask)
, invokeAll(subtask1, subtask2, ...)
или же subtask.fork()
с последующим subtask.join()
,
Все вычисления выполняются следующим образом:
MyRecursiveAction task = new MyRecursiveAction(params);
ForkJoinPool pool = new ForkJoinPool(numberOfThreads);
pool.invoke(task); // will block until task is done
К сожалению, преимущества Fork / Join имеют некоторые ограничения, например:
(...) В идеале вычисления должны избегать синхронизированных методов или блоков и должны минимизировать другую блокирующую синхронизацию, кроме присоединения к другим задачам или использования синхронизаторов, таких как Phasers, которые объявлены для взаимодействия с планированием fork/join. Подразделимые задачи также не должны выполнять блокирующий ввод-вывод и в идеале должны иметь доступ к переменным, которые полностью независимы от переменных, к которым обращаются другие выполняющиеся задачи. Эти руководящие принципы неукоснительно соблюдаются, поскольку не разрешают выбрасывать проверенные исключения, такие как IOException. (...)
Для более подробной информации см. API документы ForkJoinTask
,
Работать с Future
а не с Runnable
, Там это Future#isDone
метод, который может вам помочь.
Если у вас нет ничего значимого, чтобы вернуться из Callable
использовать Callable<Void>
а также Future<Void>
,
Если вы можете использовать Guava Futures, вы можете использовать Futures.allAsList
или же Futures.successfulAsList
, Это позволяет вам обернуть несколько Future
случаи, когда вы вернулись из ExecutorService
в один Future
который вы можете проверить, чтобы увидеть, закончил ли он, используя isDone()
(или просто get()
в этом отношении, если вы хотите заблокировать до завершения).