Die folgende Klasse umschließt einen ThreadPoolExecutor und blockiert mithilfe eines Semaphors, wenn die Arbeitswarteschlange voll ist:
public final class BlockingExecutor {
private final Executor executor;
private final Semaphore semaphore;
public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
this.semaphore = new Semaphore(queueSize + maxPoolSize);
}
private void execImpl (final Runnable command) throws InterruptedException {
semaphore.acquire();
try {
executor.execute(new Runnable() {
@Override
public void run() {
try {
command.run();
} finally {
semaphore.release();
}
}
});
} catch (RejectedExecutionException e) {
// will never be thrown with an unbounded buffer (LinkedBlockingQueue)
semaphore.release();
throw e;
}
}
public void execute (Runnable command) throws InterruptedException {
execImpl(command);
}
}
Diese Wrapper-Klasse basiert auf einer Lösung aus dem Buch Java Concurrency in Practice von Brian Goetz. Die Lösung in diesem Buch verwendet nur zwei Konstruktorparameter: eine Executor
und eine für das Semaphor verwendete Grenze. Dies wird in der Antwort von Fixpoint gezeigt. Bei diesem Ansatz gibt es ein Problem: Er kann sich in einem Zustand befinden, in dem die Pool-Threads ausgelastet sind, die Warteschlange voll ist, das Semaphor jedoch gerade eine Genehmigung freigegeben hat. (semaphore.release()
im letzten Block). In diesem Status kann eine neue Aufgabe die gerade freigegebene Berechtigung abrufen, wird jedoch abgelehnt, da die Aufgabenwarteschlange voll ist. Natürlich ist das nicht etwas, was du willst; Sie möchten in diesem Fall blockieren.
Um dies zu lösen, müssen wir eine unbegrenzte Warteschlange verwenden, wie JCiP klar erwähnt. Das Semaphor fungiert als Schutz und wirkt wie eine virtuelle Warteschlange. Dies hat den Nebeneffekt, dass das Gerät möglicherweise maxPoolSize + virtualQueueSize + maxPoolSize
Aufgaben enthalten kann. Warum ist das so? Wegen der
semaphore.release()
im Endblock. Wenn alle Pool-Threads diese Anweisung gleichzeitig aufrufen, werden maxPoolSize
Genehmigungen freigegeben, sodass dieselbe Anzahl von Aufgaben in die Einheit gelangen kann. Wenn wir eine begrenzte Warteschlange verwenden würden, wäre diese immer noch voll, was zu einer abgelehnten Aufgabe führen würde. Da wir wissen, dass dies nur auftritt, wenn ein Pool-Thread fast fertig ist, ist dies kein Problem. Wir wissen, dass der Pool-Thread nicht blockiert wird, sodass bald eine Aufgabe aus der Warteschlange entfernt wird.
Sie können jedoch eine begrenzte Warteschlange verwenden. Stellen Sie einfach sicher, dass die Größe gleich ist virtualQueueSize + maxPoolSize
. Größere Größen sind nutzlos. Das Semaphor verhindert, dass mehr Elemente eingelassen werden. Kleinere Größen führen zu abgelehnten Aufgaben. Die Wahrscheinlichkeit, dass Aufgaben abgelehnt werden, steigt mit abnehmender Größe. Angenommen, Sie möchten einen begrenzten Executor mit maxPoolSize = 2 und virtualQueueSize = 5. Nehmen Sie dann ein Semaphor mit 5 + 2 = 7 Genehmigungen und einer tatsächlichen Warteschlangengröße von 5 + 2 = 7. Die tatsächliche Anzahl der Aufgaben, die in der Einheit ausgeführt werden können, beträgt dann 2 + 5 + 2 = 9. Wenn der Executor voll ist (5 Aufgaben in der Warteschlange, 2 im Thread-Pool, also 0 Berechtigungen verfügbar) und ALLE Pool-Threads ihre Berechtigungen freigeben, können genau 2 Genehmigungen von eingehenden Aufgaben übernommen werden.
Jetzt ist die Lösung von JCiP etwas umständlich zu verwenden, da sie nicht alle diese Einschränkungen erzwingt (unbegrenzte Warteschlange oder mit diesen mathematischen Einschränkungen verbunden usw.). Ich denke, dass dies nur ein gutes Beispiel ist, um zu demonstrieren, wie Sie neue thread-sichere Klassen basierend auf den bereits verfügbaren Teilen erstellen können, aber nicht als ausgewachsene, wiederverwendbare Klasse. Ich glaube nicht, dass Letzteres die Absicht des Autors war.