Ausführen eines ExecutorService in einen Daemon in Java


76

Ich verwende einen ExecutoreService in Java 1.6, der einfach von gestartet wurde

ExecutorService pool = Executors.newFixedThreadPool(THREADS). 

Wenn mein Hauptthread fertig ist (zusammen mit allen vom Thread-Pool verarbeiteten Aufgaben), verhindert dieser Pool, dass mein Programm heruntergefahren wird, bis ich explizit aufrufe

pool.shutdown();

Kann ich vermeiden, dass ich dies aufrufen muss, indem ich die von diesem Pool verwendete interne Thread-Verwaltung irgendwie in einen Deamon-Thread verwandle? Oder fehlt mir hier etwas?


1
Ich als Autor der derzeit akzeptierten Antwort würde vorschlagen, den von Marco13 veröffentlichten Ansatz zu lesen: stackoverflow.com/a/29453160/1393766 und das Akzeptanzzeichen von meinem Beitrag in seinen zu ändern, da die dort beschriebene Lösung wahrscheinlich am einfachsten ist und sich dem nähert , was Sie ursprünglich erreichen wollten .
Pshemo

1
@Pshemo Lass mich dir nicht zustimmen ... Bitte überprüfe meinen Kommentar unter der Antwort von Marco13.
Vladimir

@Pshemo Angesichts des Hinweises von Vladimirs könnten Sie darauf hinweisen, dass die "einfache" Lösung möglicherweise nicht immer die bevorzugte ist.
Marco13

@ Marco13 Genau aus diesem Grund habe ich "wahrscheinlich" am Anfang meiner Antwort platziert. Es kann jedoch eine gute Idee sein, in Ihre Antwort weitere Informationen zu der dahinter stehenden Idee aufzunehmen, und möglicherweise einige allgemeine Hinweise, wie z. B. den Wert, der keepAliveTimeje nach Häufigkeit der Ausführung von Aufgaben angemessen sein kann .
Pshemo

Antworten:


102

Die wahrscheinlich einfachste und bevorzugte Lösung ist in der Antwort von Marco13 enthalten. Lassen Sie sich also nicht von Stimmenunterschieden (meine Antwort ist einige Jahre älter) oder Akzeptanzzeichen (es bedeutet nur, dass meine Lösung für OP-Umstände geeignet war, nicht dass sie die beste ist) täuschen.


Sie können ThreadFactoryThreads in Executor auf Daemons setzen. Dies wirkt sich auf den Executor-Service so aus, dass er auch zum Daemon-Thread wird, sodass er (und die von ihm verarbeiteten Threads) gestoppt werden, wenn kein anderer Nicht-Daemon-Thread vorhanden ist. Hier ist ein einfaches Beispiel:

ExecutorService exec = Executors.newFixedThreadPool(4,
        new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = Executors.defaultThreadFactory().newThread(r);
                t.setDaemon(true);
                return t;
            }
        });

exec.execute(YourTaskNowWillBeDaemon);

Wenn Sie jedoch einen Executor erhalten möchten, der seine Aufgabe beendet und gleichzeitig seine shutdown()Methode automatisch aufruft , wenn die Anwendung abgeschlossen ist, möchten Sie Ihren Executor möglicherweise mit Guavas umschließen MoreExecutors.getExitingExecutorService .

ExecutorService exec = MoreExecutors.getExitingExecutorService(
        (ThreadPoolExecutor) Executors.newFixedThreadPool(4), 
        100_000, TimeUnit.DAYS//period after which executor will be automatically closed
                             //I assume that 100_000 days is enough to simulate infinity
);
//exec.execute(YourTask);
exec.execute(() -> {
    for (int i = 0; i < 3; i++) {
        System.out.println("daemon");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

1
Obwohl diese Antwort akzeptiert wurde, denke ich nicht, dass dies das ist, was OP beabsichtigt hat: Ich denke, das Programm sollte heruntergefahren werden, wenn alle Aufgaben verarbeitet wurden. Wenn Sie den Thread des Executors auf 'Daemon' setzen, wird das Programm beendet, auch wenn noch Aufgaben ausgeführt werden müssen.
Arnout Engelen

@ArnoutEngelen Ich habe meine Antwort aktualisiert, um einen alternativen Ansatz zu geben, der auch dieses Problem löst, aber ich glaube, dass der Beitrag von Marco13 den besten Ansatz enthält.
Pshemo

52

Es gibt bereits eine integrierte Funktion zum Erstellen einer ExecutorService, die alle Threads nach einer bestimmten Zeit der Inaktivität beendet: Sie können eine erstellen ThreadPoolExecutor, die gewünschten Zeitinformationen übergeben und dann allowCoreThreadTimeout(true)diesen Executor-Service aufrufen :

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
public static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

BEARBEITEN Beachten Sie die Anmerkungen in den Kommentaren: Beachten Sie, dass dieser Thread-Pool-Executor beim Beenden der Anwendung nicht automatisch heruntergefahren wird. Der Executor wird nach dem Beenden der Anwendung weiter ausgeführt, jedoch nicht länger als die keepAliveTime. Wenn die Abhängigkeit von den genauen Anwendungsanforderungen keepAliveTimelänger als einige Sekunden sein muss, ist die Lösung in der Antwort von Pshemo möglicherweise besser geeignet: Wenn die Threads als Daemon-Threads festgelegt sind, werden sie sofort beendet, wenn die Anwendung beendet wird .


1
Das sind sehr interessante Informationen. +1. Wenn ich es richtig verstehe, können wir Executor-freie Thread-Ressourcen einfach schneller machen, einschließlich auch der Kern-Thread-Ressourcen. Ein Mangel an Ressourcen bedeutet jedoch nicht, dass der Executor heruntergefahren wird, wenn die letzte Ressource entfernt wird. Das Herunterfahren wird automatisch aufgerufen, wenn Executor keine Ressourcen hat (und neue Aufgaben ausgeführt werden müssen) und die Anwendung abgeschlossen ist.
Pshemo

1
@Pshemo Ja, dies ist kein "Herunterfahren" im Sinne der shutdownMethode. Ich habe das ein wenig umformuliert, hoffentlich ist es jetzt weniger zweideutig.
Marco13

3
@ Marco13 Dies ist eine interessante Funktion, ich wusste es nicht, aber ich glaube nicht, dass ich ein Problem richtig löse. Nehmen wir an, jemand erstellt mit Ihrer Methode einen ExecutorService und sendet ihm eine Aufgabe. Anschließend wird die main()Methode beendet. Nachdem die Aufgabe erledigt ist, lebt ExecutorService für keepAliveTime und wartet, bis alle Kernthreads sterben. Das heißt, wenn Sie versuchen, Ihre Java-App ordnungsgemäß zu stoppen, müssen Sie einige (möglicherweise große) Zeit warten. Ich finde es nicht gut. @Pshemo Ich glaube, deine Antwort ist derzeit die beste.
Vladimir

1
@ VladimirS. Du hast recht. Ob dieses Verhalten akzeptabel ist oder nicht, hängt möglicherweise auch vom Anwendungsmuster ab. Normalerweise gebe ich ihnen ein keepAliveTimepaar Sekunden (und kann mir zugegebenermaßen kaum vorstellen, wann eine Zeit von mehr als ein paar Sekunden angemessen oder sogar notwendig wäre). Wenn es erforderlich ist, einen größeren zu haben keepAliveTimeund dennoch sofort zu beenden, ist die Daemon-Thread-Lösung vorzuziehen.
Marco13

1
@ Marco13 Ja, du hast auch recht, es ist eine Frage der App-Anforderungen. Ich kann mir wiederum nicht vorstellen, wie man ein ExecutorServicemit allowCoreThreadTimeOut(true)und keepAliveTimeklein genug verwenden kann, um es mehr oder weniger schnell sterben zu lassen. Was ist der Vorteil von solchen ExecutorService? Nach einer kurzen Zeit der Inaktivität sterben alle Kernthreads ab, und das Senden einer neuen Aufgabe führt zu einer neuen Thread-Erstellung mit all ihrem Overhead, sodass meine Aufgabe nicht sofort gestartet wird. Es wird kein Pool mehr sein.
Vladimir

22

Ich würde Guavas ThreadFactoryBuilder-Klasse verwenden.

ExecutorService threadPool = Executors.newFixedThreadPool(THREADS, new ThreadFactoryBuilder().setDaemon(true).build());

Wenn Sie Guava noch nicht verwenden, würde ich eine ThreadFactory-Unterklasse verwenden, wie oben in Pshemos Antwort beschrieben


1
Können Sie erklären, was der Unterschied zwischen dem Ansatz Ihrer Antwort und der akzeptierten Antwort ist? Aus dem, was ich im Quellcode von ThreadFactoryBuilder eingecheckt habe, wird ThreadFactory mit genau demselben Verhalten erstellt, wie es in meiner Antwort dargestellt ist. Habe ich etwas verpasst oder war der Sinn Ihrer Antwort zu zeigen, dass wir mit Guava diesen Code ein wenig verkürzen können? Übrigens sage ich nicht, dass Ihre Antwort falsch ist, ich versuche nur, sie besser zu verstehen.
Pshemo

4
Es ist prägnanter. Wenn Sie Guava bereits verwenden, warum sollten Sie den in der Bibliothek vorhandenen Code neu schreiben? Sie haben Recht, dass es genau das Gleiche tut.
Phil Hayward

6

Wenn Sie es nur an einer Stelle verwenden möchten, können Sie die java.util.concurrent.ThreadFactoryImplementierung einbinden, z. B. für einen Pool mit 4 Threads, die Sie schreiben würden (Beispiel als Lambda unter der Annahme von Java 1.8 oder neuer):

ExecutorService pool = Executors.newFixedThreadPool(4,
        (Runnable r) -> {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
);

Normalerweise möchte ich jedoch, dass alle meine Thread-Fabriken Daemon-Threads erzeugen. Daher füge ich eine Dienstprogrammklasse wie folgt hinzu:

import java.util.concurrent.ThreadFactory;

public class DaemonThreadFactory implements ThreadFactory {

    public final static ThreadFactory instance = 
                    new DaemonThreadFactory();

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

Das erlaubt mir leicht, DaemonThreadFactory.instancezum ExecutorServicez

ExecutorService pool = Executors.newFixedThreadPool(
    4, DaemonThreadFactory.instance
);

oder verwenden Sie es, um einen Daemon- Thread einfach von einem zu starten Runnable, z

DaemonThreadFactory.instance.newThread(
    () -> { doSomething(); }
).start();

4

Ja.

Sie müssen lediglich eine eigene ThreadFactoryKlasse erstellen , die Daemon-Threads anstelle von regulären Threads erstellt.


0

Diese Lösung ähnelt der von @ Marco13, aber anstatt unsere eigene zu erstellen ThreadPoolExecutor, können wir die von zurückgegebene Lösung ändern Executors#newFixedThreadPool(int nThreads). Hier ist wie:

ExecutorService ex = Executors.newFixedThreadPool(nThreads);
 if(ex instanceof ThreadPoolExecutor){
    ThreadPoolExecutor tp = (ThreadPoolExecutor) ex;
    tp.setKeepAliveTime(time, timeUnit);
    tp.allowCoreThreadTimeOut(true);
}

1
Ein Trottel hier ist: Sie können nicht sicher sein, dass a zurückgegeben newFixedThreadPoolwird ThreadPoolExecutor. (Es tut und wird es mit ziemlicher Sicherheit für immer tun, aber das wissen Sie nicht )
Marco13

0

Sie können den ThreadFactoryBuilder von Guava verwenden. Ich wollte die Abhängigkeit nicht hinzufügen und ich wollte die Funktionalität von Executors.DefaultThreadFactory, also habe ich Komposition verwendet:

class DaemonThreadFactory implements ThreadFactory {
    final ThreadFactory delegate;

    DaemonThreadFactory() {
        this(Executors.defaultThreadFactory());
    }

    DaemonThreadFactory(ThreadFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = delegate.newThread(r);
        thread.setDaemon(true);
        return thread;
    }
}

-1

Wenn Sie eine bekannte Liste von Aufgaben haben, benötigen Sie überhaupt keine Daemon-Threads. Sie können einfach shutdown () im ExecutorService aufrufen, nachdem Sie alle Ihre Aufgaben gesendet haben.

Wenn Ihr Hauptthread abgeschlossen ist, verwenden Sie die Methode awaitTermination (), um Zeit für die Ausführung der übermittelten Aufgaben zu lassen. Die aktuell übermittelten Aufgaben werden ausgeführt, und der Thread-Pool beendet seinen Steuer-Thread, sobald sie abgeschlossen sind.

for (Runnable task : tasks) {
  threadPool.submit(task);
}
threadPool.shutdown();
/*... do other stuff ...*/
//All done, ready to exit
while (!threadPool.isTerminated()) {
  //this can throw InterruptedException, you'll need to decide how to deal with that.
  threadPool.awaitTermination(1,TimeUnit.SECOND); 
}

1
Diese Strategie unterbricht / stoppt keine Aufgaben, die einem Muster "Ausführen bis unterbrochen" folgen, in dem die Threads weiterhin ausgeführt werden (da sie nicht dämonisiert und nicht unterbrochen sind). Es ist viel sicherer, Threads auf Daemon zu setzen, wenn es wichtig ist, sie tatsächlich zu stoppen.
Krease
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.