web-dev-qa-db-fra.com

Comment attendre la fin d'un ThreadPoolExecutor

Ma question: Comment exécuter un groupe d'objets threadés sur un ThreadPoolExecutor et attendre qu'ils finissent tous avant de continuer?

Je suis nouveau sur ThreadPoolExecutor. Donc, ce code est un test pour apprendre comment cela fonctionne. À l'heure actuelle, je ne remplis même pas la BlockingQueue avec les objets car je ne comprends pas comment démarrer la file d'attente sans appeler execute() avec une autre RunnableObject. Quoi qu'il en soit, pour l'instant, j'appelle simplement awaitTermination() mais je pense qu'il me manque encore quelque chose. Tous les conseils seraient super! Merci.

public void testThreadPoolExecutor() throws InterruptedException {
  int limit = 20;
  BlockingQueue q = new ArrayBlockingQueue(limit);
  ThreadPoolExecutor ex = new ThreadPoolExecutor(limit, limit, 20, TimeUnit.SECONDS, q);
  for (int i = 0; i < limit; i++) {
    ex.execute(new RunnableObject(i + 1));
  }
  ex.awaitTermination(2, TimeUnit.SECONDS);
  System.out.println("finished");
}

La classe RunnableObject:

package playground;

public class RunnableObject implements Runnable {

  private final int id;

  public RunnableObject(int id) {
    this.id = id;
  }

  @Override
  public void run() {
    System.out.println("ID: " + id + " started");
    try {
      Thread.sleep(2354);
    } catch (InterruptedException ignore) {
    }
    System.out.println("ID: " + id + " ended");
  }
}
27
kentcdodds

Vous devriez boucler sur awaitTermination

ExecutorService threads;
// ...
// Tell threads to finish off.
threads.shutdown();
// Wait for everything to finish.
while (!threads.awaitTermination(10, TimeUnit.SECONDS)) {
  log.info("Awaiting completion of threads.");
}
47
OldCurmudgeon

Votre problème semble être que vous n'appelez pas shutdown après avoir soumis tous les travaux à votre pool. Sans shutdown() votre awaitTermination retournera toujours faux.

ThreadPoolExecutor ex =
    new ThreadPoolExecutor(limit, limit, 20, TimeUnit.SECONDS, q);
for (int i = 0; i < limit; i++) {
  ex.execute(new RunnableObject(i + 1));
}
// you are missing this line!!
ex.shutdown();
ex.awaitTermination(2, TimeUnit.SECONDS);

Vous pouvez également utiliser l'une des méthodes suivantes pour attendre que tous vos travaux soient terminés:

List<Future<Object>> futures = new ArrayList<Future<Object>>();
for (int i = 0; i < limit; i++) {
  futures.add(ex.submit(new RunnableObject(i + 1), (Object)null));
}
for (Future<Object> future : futures) {
   // this joins with the submitted job
   future.get();
}
...
// still need to shutdown at the end
ex.shutdown();

De plus, étant donné que vous dormez pendant 2354 millisecondes mais que vous attendez uniquement la fin de tous les travaux pour 2SECONDS, awaitTermination retournera toujours false.

Enfin, il semble que vous vous inquiétiez de la création d'une nouvelle ThreadPoolExecutor et que vous souhaitiez plutôt réutiliser la première. Ne sois pas. Le temps système du GC sera extrêmement minime comparé à tout code que vous écrivez pour détecter si les travaux sont terminés.


Pour citer les javadocs, ThreadPoolExecutor.shutdown():

Lance un arrêt normal dans lequel les tâches soumises précédemment sont exécutées, mais aucune nouvelle tâche ne sera acceptée. L'invocation n'a aucun effet supplémentaire si elle est déjà fermée.

Dans la méthode ThreadPoolExecutor.awaitTermination(...), il attend que l'état de l'exécuteur passe à TERMINATED. Mais d’abord, l’état doit passer à SHUTDOWN si shutdown() est appelé ou STOP si shutdownNow() est appelé.

4
Gray

Cela n'a rien à voir avec l'exécuteur lui-même. Il suffit d'utiliser l'interface Java.util.concurrent.ExecutorService.invokeAll(Collection<? extends Callable<T>>). Il bloquera jusqu'à ce que toutes les Callables soient terminées.

Les exécuteurs sont censés vivre longtemps; au-delà de la vie d'un groupe de tâches. shutdown est pour quand l'application est terminée et nettoyer.

3
artbristol

Voici une variante de la réponse acceptée qui gère les tentatives si/quand une exception InterruptedException est levée:

executor.shutdown();

boolean isWait = true;

while (isWait)
{
    try
    {             
        isWait = !executor.awaitTermination(10, TimeUnit.SECONDS);
        if (isWait)
        {
            log.info("Awaiting completion of bulk callback threads.");
        }
    } catch (InterruptedException e) {
        log.debug("Interruped while awaiting completion of callback threads - trying again...");
    }
}
2
Justin

Une autre approche consiste à utiliser CompletionService, très utile si vous devez essayer n'importe quel résultat de tâche:

//run 3 task at time
final int numParallelThreads = 3;

//I used newFixedThreadPool for convenience but if you need you can use ThreadPoolExecutor
ExecutorService executor = Executors.newFixedThreadPool(numParallelThreads);
CompletionService<String> completionService = new ExecutorCompletionService<String>(executor);

int numTaskToStart = 15;

for(int i=0; i<numTaskToStart ; i++){
    //task class that implements Callable<String> (or something you need)
    MyTask mt = new MyTask();

    completionService.submit(mt);
}

executor.shutdown(); //it cannot be queued more task

try {
    for (int t = 0; t < numTaskToStart ; t++) {
        Future<String> f = completionService.take();
        String result = f.get();
        // ... something to do ...
    }
} catch (InterruptedException e) {
    //termination of all started tasks (it returns all not started tasks in queue)
    executor.shutdownNow();
} catch (ExecutionException e) {
    // ... something to catch ...
}
1
fl4l