kann es sein, dass ein ExecutorService immer mit shutdown() beendet werden muss, damit die JVM terminiert?
In „Java Concurrency in Practice“ heißt es
Ich benutze in diesem Fall einen mit newFixedThreadPoolExecutor() erzeugten ExecutorService und wenn ich den ExecutorService nicht mit shutdown() beende, dann laufen die ThreadPool-Threads noch, obwohl keine Referenz mehr auf den ExecutorService existiert.
Minimalbeispiel:
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("ping");
}
});
}
}```
Deshalb habe ich mir folgende Hilfsmethode gebastelt:
```private void waitForWorkers(List<Future<?>> futures) {
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
// ignore if a thread threw an exception
}
}
}```
Um die zu verwenden, führe ich die Runnable()s nicht mehr mit execute(), sondern mit submit() aus und sammle die Futures in einer List.
Gibt es einen einfacheren Weg, die JVM terminieren zu lassen, sobald alle Arbeitsaufträge abgearbeitet sind?
Ja, den gibt es. Und zwar erzeugen die Runner zum Teil weitere Tasks im selben ExecutorService.
Da die zu diesem Zeitpunkt noch nicht laufen, würde der Versuch sie zu starten zu einer Exception führen.
ich glaube TO lässt sich von diesem “if a service fail to shut down” zu sehr verwirren
klar : es gibt keine referenz mehr , aber es sind auch keine deamon-threads die von der VM einfach mal so abgewürgt werden
also entweder musst du für sorgen das alle threads terminieren bzw der executor korrekt runterfährt , alles auf deamon umstellen das die VM es einfach wegwerfen kann , oder alles gewaltsam von außen abbrechen
Achso … Aber das würde ja nur der Fall sein, wenn die im selben ExecutorService gestartet würden wie die “Eltern”. Bei einem neuen ExecutorService dürfte das ja nicht passieren.
Vielmehr verwunderte mich das “could” in dem geposteten Zitat. Meine Versuche haben ja ergeben, dass selbst im einfachsten Fall die JVM nicht terminiert. Nun hatte ich gehofft, dass es irgend einen anderen impliziten Weg gibt, um die JVM stoppen zu lassen.
Wenn es keinen impliziten Weg gibt, gibt es dann einen eleganteren als den oben von mir geposteten?
Die neuen Worker werden tatsächlich vom selben ExecutorService gestartet, einen neuen erzeuge ich nicht, sondern gebe diesen im Konstruktor des Runnable mit.
Bin noch nicht ganz wach, aber … kannst du nicht einfach einen ExecutorService verwenden, der als ThreadFactory eine bekommt, die nur Deamon-Threads erstellt? Oder könnte es zum Problem werden, wenn DIE “vorzeitig” gekillt werden?
Um diesen Thread dann abzuschließen, möchte ich kurz noch meine Lösung skizzieren.
Ich habe jetzt einen [japi]Phaser[/japi] verwendet, der an der Wurzel initialisiert wird. Dieser Phaser wird an alle untergeordneten Ebenen weitergereicht, die daran dann registriert werden, bevor sie gestartet werden und sich selbst deregistrieren, sobald sie fertig sind. Vor dem executor.shutdown() warte ich dann mit [japi]Phaser#arriveAndAwaitAdvance()[/japi], dass alle gestarteten Threads fertig sind.
Das macht das hässliche Konstrukt mit den ansonsten nicht benötigten [japi]Future[/japi]s unnötig.
Übrigens habe ich nebenbei auch festgestellt, dass man, wenn man einen [japi]ThreadPoolExecutor[/japi] erstellt und eine [japi]PriorityBlockingQueue[/japi] übergibt, die Methode [japi]ExecutorService#submit()[/japi] nicht mehr verwenden kann (die Runnables werden wohl noch einmal gewrapped und können nicht mehr nach [japi]Comparable[/japi] gecasted werden).