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


200

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

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

ComputeDTaskреалізується легко. Це , як видається , правильно виконувати завдання, але код НЕ працює на wait()з IllegalMonitorStateException. Це дивно, бо я пограв з деякими прикладами іграшок, і, здається, це спрацювало.

uniquePhrasesмістить кілька десятків тисяч елементів. Чи варто використовувати інший метод? Я шукаю щось максимально просте


1
якщо ви хотіли скористатися функцією очікування (відповіді говорять, що ви цього не робите): Вам завжди потрібно синхронізувати об'єкт (у цьому випадку es), коли ви хочете його чекати - замок буде автоматично звільнений під час очікування
mihi

13
кращий спосіб ініціалізувати Executors.newFixedThreadPool(System.getRuntime().availableProcessors());

7
Runtime.getRuntime (). AvailableProcessors ();
Вік Гамов

[Це] [1] цікава альтернатива .. [1]: stackoverflow.com/questions/1250643 / ...
Резван Petruescu

1
якщо ви хочете використовувати CountDownLatch, це приклад коду: stackoverflow.com/a/44127101/4069305
Tuan Pham

Відповіді:


213

Найпростіший підхід - використовувати те, 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міститься купа Futures, яка поверне нулі (див. Визначення Executors.callable(). Мабуть, те, що ви хочете зробити, - це невеликий рефакторинг, щоб ви могли отримати корисну відповідь назад або посилання на основні ComputeDTask, але я можу Не скажу з вашого прикладу.

Якщо це не зрозуміло, зверніть увагу, що invokeAll()він не повернеться, поки всі завдання не будуть виконані. (тобто, всі запити Futureу вашій answersколекції звітуються, .isDone()якщо запитують.) Це дозволяє уникнути всіх ручних вимкнень, очікування випробувань тощо ... і дозволяє вам ExecutorServiceакуратно використовувати це протягом декількох циклів, якщо потрібно.

Є кілька пов'язаних питань щодо SO:

Жодне з них не є суто орієнтованим на ваше питання, але вони надають трохи кольорів щодо того, як люди думають Executor/ ExecutorServiceповинні бути використані.


9
Це ідеально, якщо ви додаєте всі свої завдання в пакет і ви зависаєте до списку Callables, але це не спрацює, якщо ви викликаєте ExecutorService.submit () у ситуації зворотного дзвінка чи події в циклі подій.
Desty

2
Я думаю, варто згадати, що shutdown () все-таки слід викликати, коли ExecutorService більше не потрібен, інакше потоки ніколи не закінчуються (за винятком випадків, коли corePoolSize = 0 або enableCoreThreadTimeOut = true).
John29,

дивовижний! Тільки те, що я шукав. Велике спасибі за те, що поділилися відповіддю. Дозвольте спробувати це.
MohamedSanaulla

59

Якщо ви хочете дочекатися завершення всіх завдань, скористайтеся shutdownметодом замість wait. Потім слідкуйте за цим awaitTermination.

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


27
shutdown () зупиняє ExecutorService від прийому нових завдань і закриває непрацюючі потоки робочих. Не вказується чекати завершення завершення роботи, а реалізація в ThreadPoolExecutor не чекає.
Ален О'Дея,

1
@Alain - спасибі Я повинен був згадати очікування випробування. Виправлено.
НГ.

5
Що робити, якщо для того, щоб завдання було виконано, необхідно запланувати подальші завдання? Наприклад, ви можете зробити багатопотокове обхід дерева, який передає гілки робочим ниткам. У такому випадку, оскільки ExecutorService вимкнений миттєво, він не може приймати будь-які рекурсивно заплановані завдання.
Брайан Гордон

2
awaitTerminationв якості параметра потрібен час таймауту. Хоча можна передбачити обмежений час і розмістити навколо нього петлю, щоб зачекати, поки всі нитки закінчаться, мені було цікаво, чи є більш елегантне рішення.
Abhishek S

1
Ви маєте рацію, але подивіться цю відповідь - stackoverflow.com/a/1250655/263895 - ви завжди можете надати їй неймовірно тривалий час
NG.

48

Якщо очікування ExecutorServiceзавершення всіх завдань, що закінчуються, не є саме вашою ціллю, а скоріше зачеканням, поки певна частина завдань не завершиться, ви можете використовувати CompletionService- конкретно, an ExecutorCompletionService.

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

Дозвольте це ще раз зазначити, оскільки це не очевидно з інтерфейсу: Ви повинні знати, скільки речей ви вкладете CompletionService, щоб знати, скільки речей спробувати намалювати. Особливо це має значення для take()методу: викликайте його в один раз занадто багато, і він буде блокувати ваш викликовий потік, поки якийсь інший потік не подасть інше завдання на те ж саме CompletionService.

Ось кілька прикладів, що показують, як використовуватиCompletionService у книзі Java Concurrency in Practice .


Це хороший контрапункт на мою відповідь - я б сказав, що пряма відповідь на запитання є invokeAll (); але @seh має це право, коли подає групи завдань в ES та чекає їх завершення ... --JA
andersoj

@ om-nom-nom, дякую за оновлення посилань. Я радий бачити, що відповідь все ж корисна.
seh

1
Хороша відповідь, я не знав про цеCompletionService
Вік

1
Це підхід до використання, якщо ви не хочете вимкнути існуючий ExecutorService, а просто хочете подати пакет завдань і знати, коли вони закінчені.
ToolmakerSteve

11

Якщо ви хочете зачекати, коли служба виконавця завершить виконання, зателефонуйте, shutdown()а потім, дочекайтесь випробування (одиниці, unitType) , наприклад awaitTermination(1, MINUTE). ExecutorService не блокує власний монітор, тому ви не можете користуватися waitтощо


Я думаю, це чекає випробування.
НГ.

@SB - Дякую - я бачу, що моя пам’ять є помилковою! Я оновив ім'я та додав посилання, щоб бути впевненим.
mdma

Чекати «назавжди» , використовувати його як awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack

Я думаю, що це найпростіший підхід
Шервін Асгарі

1
@MosheElisha, ви впевнені? docs.oracle.com/javase/8/docs/api/java/util/concurrent/… каже, що ініціює впорядковане вимкнення, в якому виконуються раніше подані завдання, але нові завдання не приймаються.
Хайме Хаблуцель

7

Ви можете зачекати, щоб завдання закінчились через певний інтервал:

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 ) і збирайте майбутні об’єкти, які він повертає, і викликайте 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);
   }
}

InterruptException є надзвичайно важливим для правильного поводження. Саме це дозволяє вам або користувачам вашої бібліотеки безпечно закінчити довгий процес.


6

Просто використовуйте

latch = new CountDownLatch(noThreads)

У кожній нитці

latch.countDown();

і як бар'єр

latch.await();

6

Причинна причина для IllegalMonitorStateException :

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

З вашого коду ви щойно зателефонували на wait () на ExecutorService, не володіючи замком.

Нижче код буде виправлено IllegalMonitorStateException

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

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

  1. Ітерація всіх Futureзавдань з submitна ExecutorServiceі перевірити статус з блокуванням виклику get()на Futureоб'єкті

  2. Використання invokeAll увімкненоExecutorService

  3. Використання CountDownLatch

  4. Використання ForkJoinPool або newWorkStealingPool of Executors(з Java 8)

  5. Вимкніть пул, як рекомендовано в документації на oracle сторінці

    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)що перевіряє кожні 1 хвилини.


6

Можна використовувати ExecutorService.invokeAll методом. Він виконає все завдання і дочекається, коли всі потоки закінчать своє завдання.

Ось повний явадок

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

Ось зразок коду з ExecutorService.invokeAll

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new Task1());
        taskList.add(new Task2());
        List<Future<String>> results = service.invokeAll(taskList);
        for (Future<String> f : results) {
            System.out.println(f.get());
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(2000);
            return "Task 1 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task1";
        }
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(3000);
            return "Task 2 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task2";
        }
    }
}

3

У мене також є ситуація, що у мене є набір документів, які потрібно сканувати. Я починаю з початкового "насінного" документа, який повинен бути оброблений; цей документ містить посилання на інші документи, які також повинні бути оброблені тощо.

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

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

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

Я не міг знайти нічого в JVM, що я вважав, що трохи дивно. Тому я написав клас, ThreadPoolякий можна використовувати безпосередньо або підклас, щоб додати методи, придатні для домену, наприклад schedule(Document). Сподіваюся, це допомагає!

ThreadPool Javadoc | Мейвен


Doc Link помер
Manti_Core

@Manti_Core - спасибі, оновлено.
Адріан Сміт

2

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

Ось хороший приклад: invokeAll через ExecutorService


1

Подайте свої завдання в 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();

Для його використання додайте цю залежність від градуля / мавена: 'com.github.matejtymes:javafixes:1.0'

Детальніше дивіться тут: https://github.com/MatejTymes/JavaFixes або тут: http://matejtymes.blogspot.com/2016/04/executor-that-notifies-you-when-task.html



0

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

 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.");
    }

0

Здається, вам потрібно 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's it.
}

Краса полягає в тому, pool.awaitQuiescenceде метод блокує використання потоку абонента для виконання своїх завдань, а потім повертається, коли він справді порожній.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.