it-swarm.com.de

Es ist nicht möglich, einen zwischengespeicherten Thread-Pool mit einer Größenbeschränkung zu erstellen.

Es scheint unmöglich zu sein, einen zwischengespeicherten Thread-Pool mit einer Begrenzung der Anzahl der Threads zu erstellen, die er erstellen kann.

So wird static Executors.newCachedThreadPool in der Java-Standardbibliothek implementiert:

 public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

Verwenden Sie diese Vorlage, um einen zwischengespeicherten Thread-Pool fester Größe zu erstellen:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronusQueue<Runable>());

Wenn Sie dies jetzt verwenden und 3 Aufgaben übergeben, wird alles in Ordnung sein. Durch das Übermitteln weiterer Aufgaben werden Ausführungsausnahmen zurückgewiesen.

Versuchen Sie dies:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runable>());

Wird dazu führen, dass alle Threads nacheinander ausgeführt werden. Das heißt, der Thread-Pool erstellt niemals mehr als einen Thread, um Ihre Aufgaben zu erledigen.

Dies ist ein Fehler in der Ausführungsmethode von ThreadPoolExecutor? Oder vielleicht ist das beabsichtigt? Oder gibt es einen anderen Weg?

Bearbeiten: Ich möchte etwas genau wie den zwischengespeicherten Thread-Pool (er erstellt Threads bei Bedarf und bricht sie nach einiger Zeit ab), jedoch mit einer Begrenzung der Anzahl der Threads, die erstellt werden können, und der Möglichkeit, weitere Aufgaben in die Warteschlange zu stellen, sobald er vorhanden ist stößt an sein Thread-Limit. Laut sjlees Antwort ist das unmöglich. Betrachtet man die execute () - Methode von ThreadPoolExecutor, ist dies tatsächlich unmöglich. Ich müsste ThreadPoolExecutor subclassieren und execute () überschreiben, so wie es SwingWorker tut.

112

Der ThreadPoolExecutor hat die folgenden verschiedenen Verhaltensweisen, und Ihre Probleme können durch diese Verhaltensweisen erklärt werden.

Wenn Aufgaben eingereicht werden,

  1. Wenn der Threadpool die Kerngröße nicht erreicht hat, werden neue Threads erstellt.
  2. Wenn die Kerngröße erreicht wurde und keine leeren Threads vorhanden sind, werden Tasks in die Warteschlange gestellt.
  3. Wenn die Kerngröße erreicht wurde, keine leeren Threads vorhanden sind und die Warteschlange voll ist, werden neue Threads erstellt (bis die maximale Größe erreicht ist).
  4. Wenn die maximale Größe erreicht wurde, keine leeren Threads vorhanden sind und die Warteschlange voll ist, tritt die Ablehnungsrichtlinie in Kraft.

Beachten Sie im ersten Beispiel, dass SynchronousQueue im Wesentlichen die Größe 0 hat. Sobald Sie die maximale Größe (3) erreicht haben, beginnt die Ablehnungsrichtlinie (# 4).

Im zweiten Beispiel ist die Warteschlange der Wahl eine LinkedBlockingQueue mit einer unbegrenzten Größe. Daher bleibt man bei Verhalten Nr. 2 stecken.

Sie können nicht viel mit dem zwischengespeicherten Typ oder dem festen Typ basteln, da ihr Verhalten fast vollständig bestimmt ist.

Wenn Sie einen begrenzten und dynamischen Threadpool haben möchten, müssen Sie eine positive Kerngröße und eine maximale Größe in Kombination mit einer Warteschlange mit einer begrenzten Größe verwenden. Zum Beispiel,

new ThreadPoolExecutor(10, // core size
    50, // max size
    10*60, // idle timeout
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(20)); // queue with a size

Addendum: Dies ist eine ziemlich alte Antwort, und es scheint, dass JDK sein Verhalten geändert hat, wenn es sich um die Kerngröße 0 handelt ThreadPoolExecutor fügt einen Thread hinzu, um diese Aufgabe auszuführen. Daher ist die Kerngröße von 0 eine Ausnahme von der obigen Regel. Vielen Dank Steve für bringt das mir aufgefallen ist.

214
sjlee

Wenn ich nichts verpasst habe, ist die Lösung der ursprünglichen Frage einfach. Der folgende Code implementiert das gewünschte Verhalten, wie vom ursprünglichen Poster beschrieben. Es werden bis zu 5 Threads erzeugt, um in einer unbegrenzten Warteschlange zu arbeiten. Inaktive Threads werden nach 60 Sekunden beendet.

tp = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>());
tp.allowCoreThreadTimeOut(true);
57
user1046052

Hatte das gleiche Problem. Da keine andere Antwort alle Fragen zusammenfasst, füge ich meine hinzu:

Es ist jetzt klar in docs geschrieben: Wenn Sie eine Warteschlange verwenden, die nicht blockiert ist (LinkedBlockingQueue), hat die Einstellung max. Threads keine Auswirkung.

so:

public class MyExecutor extends ThreadPoolExecutor {

    public MyExecutor() {
        super(4, 4, 5,TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        allowCoreThreadTimeOut(true);
    }

    public void setThreads(int n){
        setMaximumPoolSize(Math.max(1, n));
        setCorePoolSize(Math.max(1, n));
    }

}

Dieser Executor hat:

  1. Kein Konzept von Max-Threads, da wir eine unbegrenzte Warteschlange verwenden. Dies ist eine gute Sache, da eine solche Warteschlange dazu führen kann, dass der Executor eine große Anzahl nicht-Core-zusätzlicher Threads erstellt, wenn er seinen üblichen Richtlinien folgt.

  2. Eine Warteschlange mit maximaler Größe Integer.MAX_VALUE. Submit() löst RejectedExecutionException aus, wenn die Anzahl anstehender Aufgaben Integer.MAX_VALUE überschreitet. Ich bin mir nicht sicher, ob uns der Speicher ausgeht oder dies wird passieren.

  3. Hat 4 Kernfäden möglich. Leerlauf-Core-Threads werden automatisch beendet, wenn sie 5 Sekunden lang nicht verwendet werden. So, ja, nur bei Bedarf Thread.Number kann mit der setThreads()-Methode variiert werden.

  4. Stellt sicher, dass die minimale Anzahl von Kern-Threads nie weniger als eins beträgt, oder submit() lehnt jede Aufgabe ab. Da Core-Threads> = max-Threads sein müssen, setzt die Methode setThreads() auch max-Threads, obwohl die Einstellung für max-Threads für eine unbegrenzte Warteschlange unbrauchbar ist. 

7
S.D.

In Ihrem ersten Beispiel werden nachfolgende Tasks abgelehnt, da AbortPolicy der Standard RejectedExecutionHandler ist. Der ThreadPoolExecutor enthält die folgenden Richtlinien, die Sie über die Methode setRejectedExecutionHandler ändern können:

CallerRunsPolicy
AbortPolicy
DiscardPolicy
DiscardOldestPolicy

Es hört sich an, als wollten Sie einen Thread-Pool mit einer CallerRunsPolicy im Cache speichern.

6
brianegge

In keiner der Antworten wurde mein Problem behoben, das mit dem Erstellen einer begrenzten Anzahl von HTTP-Verbindungen mit dem HTTP-Client von Apache (3.x-Version) zusammenhängt. Da es einige Stunden gedauert hat, bis ich ein gutes Setup gefunden habe, teile ich mit:

private ExecutorService executor = new ThreadPoolExecutor(5, 10, 60L,
  TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
  Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());

Dadurch wird eine ThreadPoolExecutor erstellt, die mit fünf beginnt und maximal zehn gleichzeitig ausgeführte Threads enthält, wobei CallerRunsPolicy für die Ausführung verwendet wird.

5
paul

Per Javadoc für ThreadPoolExecutor:

Wenn mehr als corePoolSize, aber weniger als maximumPoolSize-Threads ausgeführt werden, wird ein neuer Thread erstellt nur wenn die Warteschlange voll ist . Wenn Sie corePoolSize und maximumPoolSize gleich setzen, erstellen Sie einen Thread-Pool mit fester Größe.

(Betonung mein.)

jitters Antwort ist, was Sie wollen, obwohl meine andere Frage beantwortet. :)

3

es gibt eine weitere Option. Anstelle der neuen SynchronousQueue-Funktion können Sie auch eine beliebige andere Warteschlange verwenden. Sie müssen jedoch sicherstellen, dass die Größe 1 ist, damit der Executorservice einen neuen Thread erstellen muss.

2
Ashkrit

Das ist es, was Sie wollen (zumindest denke ich). Für eine Erklärung überprüfen Sie Jonathan Feinberg Antwort

Executors.newFixedThreadPool(int n)

Erstellt einen Thread-Pool, der eine feste Anzahl von Threads wiederverwendet, die in einer gemeinsam genutzten, nicht gebundenen Warteschlange ausgeführt werden. Zu jedem Zeitpunkt werden höchstens nThreads-Threads aktive Verarbeitungsaufgaben sein. Wenn zusätzliche Tasks übergeben werden, während alle Threads aktiv sind, warten sie in der Warteschlange, bis ein Thread verfügbar ist. Wenn ein Thread aufgrund eines Fehlers während der Ausführung vor dem Herunterfahren abgebrochen wird, tritt ein neuer an seine Stelle, falls dies zur Ausführung nachfolgender Aufgaben erforderlich ist. Die Threads im Pool sind so lange vorhanden, bis sie explizit heruntergefahren werden.

2
jitter

Sieht nicht so aus, als würde eine der Antworten tatsächlich die Frage beantworten - tatsächlich sehe ich keine Möglichkeit dazu - selbst wenn Sie von PooledExecutorService eine Unterklasse bilden, da viele Methoden/Eigenschaften privat sind, z. Wenn Sie addIfUnderMaximumPoolSize erstellen, können Sie Folgendes tun:

class MyThreadPoolService extends ThreadPoolService {
    public void execute(Runnable run) {
        if (poolSize() == 0) {
            if (addIfUnderMaximumPoolSize(run) != null)
                return;
        }
        super.execute(run);
    }
}

Das nächste, was ich bekam, war das - aber auch das ist keine sehr gute Lösung

new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) {
    public void execute(Runnable command) {
        if (getPoolSize() == 0 && getActiveCount() < getMaximumPoolSize()) {        
            super.setCorePoolSize(super.getCorePoolSize() + 1);
        }
        super.execute(command);
    }

    protected void afterExecute(Runnable r, Throwable t) {
         // nothing in the queue
         if (getQueue().isEmpty() && getPoolSize() > min) {
             setCorePoolSize(getCorePoolSize() - 1);
         }
    };
 };

p.s. das obige nicht getestet

2
Stuart

Hier ist eine andere Lösung. Ich denke, dass sich diese Lösung so verhält, wie Sie möchten (wenn auch nicht stolz auf diese Lösung):

final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    public boolean offer(Runnable o) {
        if (size() > 1)
            return false;
        return super.offer(o);
    };

    public boolean add(Runnable o) {
        if (super.offer(o))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
};

RejectedExecutionHandler handler = new RejectedExecutionHandler() {         
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        queue.add(r);
    }
};

dbThreadExecutor =
        new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, queue, handler);
1
Stuart

Das Problem wurde wie folgt zusammengefasst:

Ich möchte so etwas wie den zwischengespeicherten Thread-Pool (er erstellt Threads bei Bedarf und bricht sie nach einer gewissen Zeit ab), aber mit einer Begrenzung der Anzahl der Threads, die er erstellen kann, und der Möglichkeit, weitere Tasks in die Warteschlange zu stellen, sobald er seine erreicht hat Thread-Limit.

Bevor ich auf die Lösung zeige, erkläre ich, warum die folgenden Lösungen nicht funktionieren:

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());

Dadurch werden keine Tasks in die Warteschlange gestellt, wenn das Limit von 3 erreicht ist, da SynchronousQueue per Definition keine Elemente enthalten kann.

new ThreadPoolExecutor(0, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

Dadurch wird nicht mehr als ein einzelner Thread erstellt, da ThreadPoolExecutor nur Threads erstellt, die corePoolSize überschreiten, wenn die Warteschlange voll ist. LinkedBlockingQueue ist jedoch nie voll.

ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<Runnable>());
executor.allowCoreThreadTimeOut(true);

Dies wird keine Threads wiederverwenden, bis die CorePoolSize erreicht wurde, da ThreadPoolExecutor die Anzahl der Threads erhöht, bis die CorePoolSize erreicht ist, auch wenn vorhandene Threads inaktiv sind. Wenn Sie mit diesem Nachteil leben können, ist dies die einfachste Lösung für das Problem. Dies ist auch die in "Java Concurrency in Practice" (Fußnote auf S. 172) beschriebene Lösung.

Die einzige vollständige Lösung für das beschriebene Problem scheint darin zu bestehen, die offer -Methode der Warteschlange zu überschreiben und ein RejectedExecutionHandler zu schreiben, wie in den Antworten auf diese Frage erläutert: So erhalten Sie den ThreadPoolExecutor zu Threads vor dem Anstehen auf Maximum erhöhen?

0
  1. Sie können ThreadPoolExecutor verwenden, wie von @sjlee vorgeschlagen.

    Sie können die Größe des Pools dynamisch steuern. Sehen Sie sich diese Frage für weitere Details an: 

    Dynamic Thread Pool

    OR

  2. Sie können die mit Java 8 eingeführte newWorkStealingPool API verwenden.

    public static ExecutorService newWorkStealingPool()
    

    Erstellt einen Worksteal-Thread-Pool, der alle verfügbaren Prozessoren als Zielparallelitätsebene verwendet.

Standardmäßig ist die Parallelitätsebene auf die Anzahl der CPU-Kerne in Ihrem Server festgelegt. Wenn Sie über einen 4-Core-CPU-Server verfügen, wäre die Thread-Pool-Größe 4. Diese API gibt den ForkJoinPool-Typ von ExecutorService zurück und ermöglicht das Stehlen von leeren Threads, indem Aufgaben aus ausgelasteten Threads in ForkJoinPool gestohlen werden. 

0
Ravindra babu