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?
java
multithreading
executorservice
Leeeroy
quelle
quelle
interface ExecutorService
, die ebenfallsThreadPoolExecutor
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:Dies reicht aus, um dies zu beantworten:
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.
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.
quelle
Ihre Frage ist eher offen: Die
ExecutorService
Benutzeroberfläche garantiert lediglich, dass irgendwo ein Thread die übermittelte InstanzRunnable
oderCallable
Instanz verarbeitet.Wenn die eingereichten
Runnable
/Callable
verweist auf eine gemeinsam genutzte Datenstruktur , die von anderen zugänglich istRunnable
/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 wurdeExecutorService
(und der Rest vonjava.util.concurrent
) entwickelt.quelle
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. DochThreadPoolExecutor
beide sind und eindeutig als Thread-sicher dokumentiert. Darüber hinaus wirdThreadPoolExecutor
die Jobwarteschlange überjava.util.concurrent.BlockingQueue
eine Schnittstelle verwaltet, die anfordert, dass alle Implementierungen threadsicher sind. Jedejava.util.concurrent.*
Implementierung vonBlockingQueue
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 eineBlockingQueue
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.
quelle
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; }
quelle
Entgegen der Antwort von Luke Usherwood wird in der Dokumentation nicht impliziert, dass
ExecutorService
Implementierungen garantiert threadsicher sind. Bezüglich der Frage vonThreadPoolExecutor
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 dascount
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.
quelle