Ist die ExecutorService
Garantie Thread - Sicherheit?
Ich werde Jobs von verschiedenen Threads an denselben ThreadPoolExecutor senden. Muss ich den Zugriff auf den Executor synchronisieren, bevor ich Aufgaben interagiere / sende?
Antworten:
Es ist wahr, die fraglichen JDK-Klassen scheinen keine explizite Garantie für die Übermittlung von threadsicheren Aufgaben zu geben. In der Praxis sind jedoch alle ExecutorService-Implementierungen in der Bibliothek auf diese Weise tatsächlich threadsicher. Ich denke, es ist vernünftig, sich darauf zu verlassen. Da der gesamte Code, der diese Funktionen implementiert, öffentlich zugänglich gemacht wurde, gibt es für niemanden eine Motivation, ihn auf eine andere Weise vollständig neu zu schreiben.
interface ExecutorService
, die ebenfalls ThreadPoolExecutor
eingehalten werden muss. (Weitere Details in meiner kürzlich aktualisierten Antwort.)
( Im Gegensatz zu anderen Antworten) der Thread-Sicherheit Vertrag wird dokumentiert: Blick in dem interface
javadocs (im Gegensatz zu javadoc von Methoden gegen). Am Ende des ExecutorService- Javadocs finden Sie beispielsweise:
Auswirkungen auf die Speicherkonsistenz: Aktionen in einem Thread vor der Übermittlung einer ausführbaren oder aufrufbaren Aufgabe an einen ExecutorService werden ausgeführt , bevor alle von dieser Aufgabe ausgeführten Aktionen ausgeführt werden, bevor das Ergebnis über Future.get () abgerufen wird.
Dies reicht aus, um dies zu beantworten:
"Muss ich den Zugriff auf den Executor synchronisieren, bevor ich Aufgaben interagiere / sende?"
Nein, tust du nicht. Es ist in Ordnung, Jobs ExecutorService
ohne externe Synchronisation zu erstellen und an alle (korrekt implementierten) zu senden . Dies ist eines der wichtigsten Designziele.
ExecutorService
ist ein gleichzeitiges Dienstprogramm, das heißt, es ist so konzipiert, dass es für die Leistung zum größten Teil ohne Synchronisierung arbeitet. (Die Synchronisierung führt zu Thread-Konflikten, die die Multithreading-Effizienz beeinträchtigen können - insbesondere beim Skalieren auf eine große Anzahl von Threads.)
Es gibt keine Garantie dafür, zu welchem Zeitpunkt in der Zukunft die Aufgaben ausgeführt oder abgeschlossen werden (einige werden möglicherweise sogar sofort auf demselben Thread ausgeführt, der sie gesendet hat). Es wird jedoch garantiert, dass der Arbeitsthread alle Auswirkungen gesehen hat, die der übermittelnde Thread bis zum ausgeführt hat Einreichungspunkt . Daher kann (der Thread, der ausgeführt wird) Ihre Aufgabe auch alle Daten, die für ihre Verwendung erstellt wurden, ohne Synchronisierung, threadsichere Klassen oder andere Formen der "sicheren Veröffentlichung" sicher lesen. Das Übermitteln der Aufgabe reicht selbst für eine "sichere Veröffentlichung" der Eingabedaten an die Aufgabe aus. Sie müssen nur sicherstellen, dass die Eingabedaten während der Ausführung der Aufgabe in keiner Weise geändert werden.
Wenn Sie das Ergebnis der Aufgabe über Future.get()
zurückrufen, werden dem abrufenden Thread garantiert alle vom Worker-Thread des Executors vorgenommenen Effekte angezeigt (sowohl im zurückgegebenen Ergebnis als auch in den vom Worker-Thread vorgenommenen Änderungen an Nebenwirkungen). .
Dieser Vertrag impliziert auch, dass es für die Aufgaben selbst in Ordnung ist, mehr Aufgaben einzureichen.
"Garantiert der ExecutorService die Thread-Sicherheit?"
Jetzt ist dieser Teil der Frage viel allgemeiner. Zum Beispiel konnte keine Aussage eines Thread-Sicherheitsvertrags über die Methode gefunden werden shutdownAndAwaitTermination
- obwohl ich feststelle, dass das Codebeispiel im Javadoc keine Synchronisation verwendet. (Obwohl es vielleicht eine versteckte Annahme gibt, dass das Herunterfahren von demselben Thread ausgelöst wird, der den Executor erstellt hat, und nicht von einem Arbeitsthread?)
Übrigens würde ich das Buch "Java Concurrency In Practice" für einen guten Einblick in die Welt der gleichzeitigen Programmierung empfehlen.
Ihre Frage ist eher offen: Die ExecutorService
Benutzeroberfläche garantiert lediglich, dass irgendwo ein Thread die übermittelte Instanz Runnable
oder Callable
Instanz verarbeitet.
Wenn die eingereichten Runnable
/ Callable
verweist auf eine gemeinsam genutzte Datenstruktur , die von anderen zugänglich ist Runnable
/ Callable
s Instanzen (möglicherweise durch verschiedene Threads unerwuenscht verarbeitet wird), dann ist es in Ihrer Verantwortung , Thread - Sicherheit in dieser Datenstruktur zu gewährleisten.
Um den zweiten Teil Ihrer Frage zu beantworten, haben Sie Zugriff auf den ThreadPoolExecutor, bevor Sie Aufgaben senden. z.B
BlockingQueue<Runnable> workQ = new LinkedBlockingQueue<Runnable>();
ExecutorService execService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.SECONDS, workQ);
...
execService.submit(new Callable(...));
BEARBEITEN
Basierend auf Brians Kommentar und für den Fall, dass ich Ihre Frage falsch verstanden habe: Die Übermittlung von Aufgaben von mehreren Produzenten-Threads an das ExecutorService
ist normalerweise threadsicher (obwohl dies, soweit ich das beurteilen kann, nicht explizit in der API der Schnittstelle erwähnt wird). Jede Implementierung, die keine Thread-Sicherheit bietet, wäre in einer Umgebung mit mehreren Threads nutzlos (da mehrere Hersteller / mehrere Verbraucher ein ziemlich verbreitetes Paradigma sind), und genau dafür wurde ExecutorService
(und der Rest von java.util.concurrent
) entwickelt.
Für ThreadPoolExecutor
die Antwort ist einfach ja . ExecutorService
übernimmt keine Garantie oder anderweitige Garantie dafür, dass alle Implementierungen threadsicher sind, und kann dies nicht, da es sich um eine Schnittstelle handelt. Diese Vertragsarten liegen außerhalb des Bereichs einer Java-Schnittstelle. Doch ThreadPoolExecutor
beide sind und eindeutig als Thread-sicher dokumentiert. Darüber hinaus wird ThreadPoolExecutor
die Jobwarteschlange über java.util.concurrent.BlockingQueue
eine Schnittstelle verwaltet, die anfordert, dass alle Implementierungen threadsicher sind. Jede java.util.concurrent.*
Implementierung von BlockingQueue
kann sicher als threadsicher angenommen werden. Eine nicht standardmäßige Implementierung ist möglicherweise nicht möglich, obwohl dies völlig albern wäre, wenn jemand eine BlockingQueue
Implementierungswarteschlange bereitstellen würde, die nicht threadsicher ist.
Die Antwort auf Ihre Titelfrage lautet also eindeutig Ja . Die Antwort auf den nachfolgenden Teil Ihrer Frage lautet wahrscheinlich , da zwischen den beiden einige Diskrepanzen bestehen.
List.hashCode()
). Die Javadocs sagen "BlockingQueue-Implementierungen sind threadsicher" (eine nicht threadsichere BlockingQueue ist also nicht nur albern, sondern auch fehlerhaft), aber es gibt keine solche Dokumentation für ThreadPoolExecutor oder eine der von ihr implementierten Schnittstellen.
ThreadPoolExecutor
threadsicher ist?
Für ThreadPoolExecutor ist die Übermittlung threadsicher. Sie können den Quellcode in jdk8 sehen. Beim Hinzufügen einer neuen Aufgabe wird ein mainLock verwendet, um die Thread-Sicherheit zu gewährleisten.
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Entgegen der Antwort von Luke Usherwood wird in der Dokumentation nicht impliziert, dass ExecutorService
Implementierungen garantiert threadsicher sind. Bezüglich der Frage von ThreadPoolExecutor
spezifisch siehe andere Antworten.
Ja, es wird eine Vor-Ort- Beziehung angegeben, dies bedeutet jedoch nichts über die Thread-Sicherheit der Methoden selbst, wie von Miles kommentiert . In der Antwort von Luke Usherwood heißt es, dass Ersteres ausreicht, um Letzteres zu beweisen, aber es wird kein tatsächliches Argument vorgebracht.
"Thread-Sicherheit" kann verschiedene Bedeutungen haben, aber hier ist ein einfaches Gegenbeispiel für eine Executor
(nicht, ExecutorService
aber es macht keinen Unterschied), die die erforderliche Vor-Ort- Beziehung trivial erfüllt, aber aufgrund des nicht synchronisierten Zugriffs auf das count
Feld nicht thread-sicher ist .
class CountingDirectExecutor implements Executor {
private int count = 0;
public int getExecutedTaskCount() {
return count;
}
public void execute(Runnable command) {
command.run();
}
}
Haftungsausschluss: Ich bin kein Experte und habe diese Frage gefunden, weil ich selbst nach der Antwort gesucht habe.