ExecutorService, как ждать завершения всех задач

Каков самый простой способ дождаться завершения всех задач ExecutorService? Моя задача в первую очередь вычисляется, поэтому я просто хочу запустить большое количество заданий - по одному на каждое ядро. Сейчас моя настройка выглядит так:

ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {   
    es.execute(new ComputeDTask(singleTable));
}
try{
    es.wait();
} 
catch (InterruptedException e){
    e.printStackTrace();
}

ComputeDTask реализует runnable. Кажется, что выполнение задач выполняется правильно, но код падает на wait() с помощью IllegalMonitorStateException. Это странно, потому что я играл с некоторыми примерами игрушек и, похоже, работал.

uniquePhrases содержит несколько десятков тысяч элементов. Должен ли я использовать другой метод? Я ищу что-то как можно проще

Ответ 1

Самый простой способ - использовать ExecutorService.invokeAll(), который делает то, что вы хотите в однострочном. На вашем языке вам нужно будет изменить или обернуть ComputeDTask, чтобы реализовать Callable<>, что может дать вам довольно большую гибкость. Вероятно, в вашем приложении есть значимая реализация Callable.call(), но вот способ обернуть его, если не использовать Executors.callable().

ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());

for (DataTable singleTable: uniquePhrases) { 
    todo.add(Executors.callable(new ComputeDTask(singleTable))); 
}

List<Future<Object>> answers = es.invokeAll(todo);

Как указывали другие, вы можете использовать версию тайм-аута invokeAll(), если это необходимо. В этом примере answers будет содержать пучок Future, который будет возвращать значения NULL (см. Определение Executors.callable(). Возможно, что вы хотите сделать, это небольшой рефакторинг, чтобы вы могли получить полезный ответ или ссылку на базовый ComputeDTask, но я не могу сказать из вашего примера.

Если это неясно, обратите внимание, что invokeAll() не вернется, пока все задачи не будут завершены. (т.е. все Future в вашей коллекции answers будут сообщать .isDone(), если задано.) Это позволяет избежать всего ручного отключения, waitTermination и т.д.... и позволяет повторно использовать этот ExecutorService аккуратно для нескольких циклов, при желании.

Есть несколько связанных вопросов по SO:

Ни один из них не является строгим для вашего вопроса, но они дают немного цвета о том, как люди думают, что Executor/ExecutorService следует использовать.

Ответ 2

Если вы хотите дождаться завершения всех задач, используйте shutdown вместо wait. Затем следуйте за ней с помощью awaitTermination.

Кроме того, вы можете использовать Runtime.availableProcessors, чтобы получить количество аппаратных потоков, чтобы вы могли правильно инициализировать свой поток.

Ответ 3

Если ожидание выполнения всех задач в ExecutorService не является вашей целью, а скорее ждет завершения определенной партии задач, вы можете использовать CompletionService — в частности, ExecutorCompletionService.

Идея заключается в создании ExecutorCompletionService упаковки Executor, submit некоторого известного количества задач с помощью CompletionService, затем нарисуйте то же количество результатов из очереди завершения, используя take() (которые блокируют) или poll() (чего нет). После того, как вы нарисуете все ожидаемые результаты, соответствующие поставленным задачам, вы знаете, что все сделано.

Позвольте мне сказать это еще раз, потому что это не очевидно из интерфейса: вы должны знать, сколько вещей вы положили в CompletionService, чтобы узнать, сколько вещей попытаться извлечь. Это особенно важно с помощью метода take(): назовите его один раз слишком много, и он заблокирует ваш вызывающий поток, пока какой-либо другой поток не отправит другое задание на тот же CompletionService.

Есть несколько примеров, показывающих, как использовать CompletionService в книге Java Concurrency на практике.

Ответ 4

Если вы хотите дождаться завершения выполнения службы-исполнителя, вызовите shutdown(), а затем waitTermination (units, unitType), например awaitTermination(1, MINUTE). ExecutorService не блокирует его собственный монитор, поэтому вы не можете использовать wait и т.д.

Ответ 5

Вы можете ждать выполнения заданий на определенный интервал:

int maxSecondsPerComputeDTask = 20;
try {
    while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
        // consider giving up with a 'break' statement under certain conditions
    }
} catch (InterruptedException e) {
    throw new RuntimeException(e);    
}

Или вы можете использовать ExecutorService. отправить (Runnable) и собрать объекты Future, которые он возвращает, и вызвать по очереди get(), чтобы дождаться их завершения.

ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
    futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
   try {
       future.get();
   } catch (InterruptedException e) {
       throw new RuntimeException(e);
   } catch (ExecutionException e) {
       throw new RuntimeException(e);
   }
}

InterruptedException крайне важно правильно обрабатывать. Это то, что позволяет вам или пользователям вашей библиотеки безопасно завершить длительный процесс.

Ответ 6

У меня также есть ситуация, когда у меня есть набор документов для обхода. Я начинаю с начального "семенного" документа, который должен быть обработан, этот документ содержит ссылки на другие документы, которые также должны обрабатываться, и т.д.

В моей основной программе я просто хочу написать что-то вроде следующего, где Crawler управляет связкой потоков.

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

То же самое произойдет, если я захочу перейти по дереву; я бы появился в корневом каталоге node, процессор для каждого node по мере необходимости добавлял бы детей в очередь, а куча нитей обрабатывала бы все узлы в дереве, пока их не было больше.

Я не мог найти ничего в JVM, который, как я думал, был немного удивителен. Поэтому я написал класс AutoStopThreadPool, который можно использовать непосредственно или подкласс для добавления методов, подходящих для домена, например. schedule(Document). Надеюсь, это поможет!

AutoStopThreadPool Javadoc | Загрузить

Ответ 7

Просто используйте

latch = new CountDownLatch(noThreads)

В каждом потоке

latch.countDown();

и как барьер

latch.await();

Ответ 8

Корневая причина для IllegalMonitorStateException:

Выброшено, чтобы указать, что поток попытался подождать на мониторе объекта или уведомить другие потоки, ожидающие мониторинга объекта, не владея указанным монитором.

Из вашего кода вы только что вызвали wait() в ExecutorService, не имея блокировки.

Ниже код исправит IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

Следуйте одному из нижеприведенных подходов, чтобы дождаться завершения всех задач, которые были отправлены на ExecutorService.

  • Итерации через все Future задачи из submit на ExecutorService и проверьте статус с блокирующим вызовом get() на Future object

  • Используя invokeAll на ExecutorService

  • Использование CountDownLatch

  • Использование ForkJoinPool или newWorkStealingPool of Executors (поскольку java 8)

  • Выключение пула, как рекомендовано в документации оракула страница

    void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
       // Wait a while for existing tasks to terminate
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
       }
    } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
    }
    

    Если вы хотите изящно дождаться завершения всех задач, когда вы используете опцию 5 вместо опций с 1 по 4, измените

    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
    

    к

    a while(condition), который проверяет каждую минуту.

Ответ 9

Добавьте все темы в коллекцию и отправьте ее с помощью invokeAll. Если вы можете использовать invokeAll метод ExecutorService, JVM не перейдет к следующей строке, пока все потоки не будут завершены.

Вот хороший пример: invokeAll через ExecutorService

Ответ 10

Вы можете использовать ThreadPoolExecutor с размером пула, установленным в Runtime. getRuntime(). availableProcessors() и .execute() все задачи, которые вы хотите выполнить, затем вызовите tpe.shutdown(), а затем подождите в while(!tpe.terminated()) { /* waiting for all tasks to complete */}, который блокирует выполнение всех поставленных задач. где tpe является ссылкой на ваш экземпляр ThreadPoolExecutor.

Или, если более целесообразно использовать ExecutorCompletionService A CompletionService, который использует поставляемый Executor для выполнения задач. Этот класс организует отправку заданий по завершении, которые размещаются в очереди, доступной с помощью take. Класс является достаточно легким, чтобы быть пригодным для временного использования при обработке групп задач.

ПРИМЕЧАНИЕ.. Эти решения не блокируются, как ExecutorService.invokeAll(), пока ждут завершения всех заданий. Таким образом, хотя самый простой он также является самым ограниченным, так как он блокирует основной поток приложения. Нехорошо, если вы хотите получить статус о том, что происходит, или делать другие вещи, пока эти задания работают, например, после обработки результатов или производства и инкрементный агрегированный результат.

Ответ 11

Простой альтернативой этому является использование потоков вместе с соединением. См. Присоединение тем

Ответ 12

Отправьте свои задачи в Runner, а затем ждите вызов метода waitTillDone() следующим образом:

Runner runner = Runner.runner(2);

for (DataTable singleTable : uniquePhrases) {

    runner.run(new ComputeDTask(singleTable));
}

// blocks until all tasks are finished (or failed)
runner.waitTillDone();

runner.shutdown();

Чтобы использовать его, добавьте эту зависимость gradle/maven: 'com.github.matejtymes:javafixes:1.0'

Подробнее см. здесь https://github.com/MatejTymes/JavaFixes или здесь: http://matejtymes.blogspot.com/2016/04/executor-that-notifies-you-when-task.html

Ответ 13

Я просто подожду, пока исполнитель закончит с указанным таймаутом, который, по вашему мнению, подходит для выполнения задач.

 try {  
         //do stuff here 
         exe.execute(thread);
    } finally {
        exe.shutdown();
    }
    boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
    if (!result)

    {
        LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
    }

Ответ 14

Похоже, вам нужно ForkJoinPool и использовать глобальный пул для выполнения задач.

public static void main(String[] args) {
    // the default `commonPool` should be sufficient for many cases.
    ForkJoinPool pool = ForkJoinPool.commonPool(); 
    // The root of your task that may spawn other tasks. 
    // Make sure it submits the additional tasks to the same executor that it is in.
    Runnable rootTask = new YourTask(pool); 
    pool.execute(rootTask);
    pool.awaitQuiescence(...);
    // that it.
}

Красота находится в pool.awaitQuiescence, где метод блокирует использование потока вызывающего объекта для выполнения его задач, а затем возвращается, когда он действительно пуст.

Ответ 15

Извините за поздний ответ, но вы можете использовать следующее, чтобы заставить threadPoolTaskExecutor дождаться обработки всех активных и ожидающих задач.

ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) AppContext.getBean("threadPoolTaskExecutor");

//Код здесь для выполнения примера runnable:

while(iterator.hasNext())
{
threadPoolTaskExecutor.execute(new Runnable() ... );
}

    while(threadPoolTaskExecutor.getActiveCount() > 0)
    {
        //Hold until finish
    }

    threadPoolTaskExecutor.shutdown();

Убедитесь, что ThreadPoolTaskExecutor bean определяется с помощью области действия = "Prototype". Важно!