Wie funktioniert atomar / flüchtig / synchronisiert intern?
Was ist der Unterschied zwischen den folgenden Codeblöcken?
Code 1
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Code 2
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Code 3
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Funktioniert es volatile
folgendermaßen? Ist
volatile int i = 0;
void incIBy5() {
i += 5;
}
gleichwertig
Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Ich denke, dass zwei Threads nicht gleichzeitig in einen synchronisierten Block eintreten können ... habe ich recht? Wenn dies zutrifft, wie funktioniert es atomic.incrementAndGet()
dann ohne synchronized
? Und ist es threadsicher?
Und was ist der Unterschied zwischen internem Lesen und Schreiben in flüchtige Variablen / atomare Variablen? Ich habe in einem Artikel gelesen, dass der Thread eine lokale Kopie der Variablen hat - was ist das?
Antworten:
Sie fragen speziell, wie sie intern funktionieren . Hier sind Sie also:
Keine Synchronisation
Es liest im Grunde genommen Werte aus dem Speicher, erhöht sie und speichert sie wieder im Speicher. Dies funktioniert in einem einzelnen Thread, aber heutzutage funktioniert es im Zeitalter von Multi-Core-, Multi-CPU- und Multi-Level-Caches nicht mehr richtig. Zunächst wird die Race-Bedingung (mehrere Threads können den Wert gleichzeitig lesen), aber auch Sichtbarkeitsprobleme eingeführt. Der Wert wird möglicherweise nur im " lokalen " CPU-Speicher (etwas Cache) gespeichert und ist für andere CPUs / Kerne (und damit - Threads) nicht sichtbar. Aus diesem Grund beziehen sich viele auf die lokale Kopie einer Variablen in einem Thread. Es ist sehr unsicher. Betrachten Sie diesen beliebten, aber fehlerhaften Code zum Stoppen von Threads:
volatile
Zurstopped
Variablen hinzufügen und es funktioniert einwandfrei - wenn ein anderer Thread geändert wirdstopped
Variable über einepleaseStop()
Methode , wird diese Änderung garantiert sofort in derwhile(!stopped)
Schleife des Arbeitsthreads angezeigt . Übrigens ist dies auch keine gute Möglichkeit, einen Thread zu unterbrechen. Weitere Informationen finden Sie unter: So stoppen Sie einen Thread, der für immer ohne Verwendung ausgeführt wird, und Stoppen eines bestimmten Java-Threads .AtomicInteger
Die
AtomicInteger
Klasse verwendet CAS -CPU-Operationen ( Compare-and-Swap ) auf niedriger Ebene (keine Synchronisierung erforderlich!). Mit ihnen können Sie eine bestimmte Variable nur ändern, wenn der aktuelle Wert gleich etwas anderem ist (und erfolgreich zurückgegeben wird). Wenn Sie es also ausführen, wirdgetAndIncrement()
es tatsächlich in einer Schleife ausgeführt (vereinfachte reale Implementierung):Also im Grunde: lesen; Versuchen Sie, einen inkrementierten Wert zu speichern. Wenn dies nicht erfolgreich ist (der Wert ist nicht mehr gleich
current
), lesen Sie es und versuchen Sie es erneut. DascompareAndSet()
ist in nativem Code (Assembly) implementiert.volatile
ohne SynchronisationDieser Code ist nicht korrekt. Es behebt das Sichtbarkeitsproblem (
volatile
stellt sicher, dass andere Threads Änderungen sehen können, an denen Änderungen vorgenommen wurdencounter
), hat jedoch weiterhin eine Race-Bedingung. Dies wurde mehrfach erklärt : Pre / Post-Inkrementierung ist nicht atomar.Die einzige Nebenwirkung von
volatile
ist das " Leeren " von Caches, sodass alle anderen Parteien die aktuellste Version der Daten sehen. Dies ist in den meisten Situationen zu streng. Deshalbvolatile
ist nicht Standard.volatile
ohne Synchronisation (2)Das gleiche Problem wie oben, aber noch schlimmer, weil
i
es nicht istprivate
. Die Rennbedingung ist noch vorhanden. Warum ist das ein Problem? Wenn beispielsweise zwei Threads diesen Code gleichzeitig ausführen, lautet die Ausgabe möglicherweise+ 5
oder+ 10
. Sie werden jedoch garantiert die Änderung sehen.Mehrfach unabhängig
synchronized
Überraschung, dieser Code ist auch falsch. In der Tat ist es völlig falsch. Zunächst synchronisieren Sie auf
i
, was geändert werden soll (außerdemi
ist es ein Grundelement, also denke ich, dass Sie auf einer temporären Seite synchronisierenInteger
das über Autoboxing erstellt wurde ...). Vollständig fehlerhaft. Sie könnten auch schreiben:Keine zwei Threads können denselben
synchronized
Block betreten mit derselben Sperre . In diesem Fall (und ähnlich in Ihrem Code) ändert sich das Sperrobjekt bei jeder Ausführungsynchronized
effektiv keine Auswirkung.Selbst wenn Sie eine endgültige Variable (oder
this
) für die Synchronisation verwendet haben, ist der Code immer noch falsch. Zwei Fäden können zuerst gelesen ,i
umtemp
synchron (mit dem gleichen Wert in lokaltemp
), dann werden die ersten ordnet einen neuen Werti
(sagen wir von 1 bis 6) und die andere tut dasselbe (1 bis 6).Die Synchronisation muss vom Lesen bis zum Zuweisen eines Wertes reichen. Ihre erste Synchronisation hat keine Auswirkung (das Lesen von
int
ist atomar) und die zweite ebenfalls. Meiner Meinung nach sind dies die richtigen Formen:quelle
compareAndSet
ist nur ein dünner Wrapper um den CAS-Betrieb. Ich gehe in meiner Antwort auf einige Details ein.Wenn Sie eine Variable als flüchtig deklarieren, wirkt sich das Ändern ihres Werts sofort auf den tatsächlichen Speicher der Variablen aus. Der Compiler kann keine Verweise auf die Variable optimieren. Dies garantiert, dass alle anderen Threads den neuen Wert sofort sehen, wenn ein Thread die Variable ändert. (Dies ist für nichtflüchtige Variablen nicht garantiert.)
Das Deklarieren einer atomaren Variablen garantiert, dass Operationen, die an der Variablen ausgeführt werden, atomar ablaufen, dh dass alle Teilschritte der Operation innerhalb des Threads abgeschlossen sind, in dem sie ausgeführt werden, und nicht von anderen Threads unterbrochen werden. Beispielsweise erfordert eine Inkrementierungs- und Testoperation, dass die Variable inkrementiert und dann mit einem anderen Wert verglichen wird. Eine atomare Operation garantiert, dass beide Schritte so ausgeführt werden, als wären sie eine einzige unteilbare / unterbrechungsfreie Operation.
Durch das Synchronisieren aller Zugriffe auf eine Variable kann jeweils nur ein Thread auf die Variable zugreifen, und alle anderen Threads müssen warten, bis dieser Zugriffsthread seinen Zugriff auf die Variable freigibt.
Der synchronisierte Zugriff ähnelt dem atomaren Zugriff, die atomaren Operationen werden jedoch im Allgemeinen auf einer niedrigeren Programmierebene implementiert. Es ist auch durchaus möglich, nur einige Zugriffe auf eine Variable zu synchronisieren und zuzulassen, dass andere Zugriffe nicht synchronisiert werden (z. B. alle Schreibvorgänge in eine Variable synchronisieren, aber keinen der Lesevorgänge daraus).
Atomizität, Synchronisation und Volatilität sind unabhängige Attribute, werden jedoch normalerweise in Kombination verwendet, um eine ordnungsgemäße Thread-Zusammenarbeit für den Zugriff auf Variablen zu erzwingen.
Nachtrag (April 2016)
Der synchronisierte Zugriff auf eine Variable wird normalerweise mithilfe eines Monitors oder Semaphors implementiert . Hierbei handelt es sich um Mutex- Mechanismen auf niedriger Ebene (gegenseitiger Ausschluss), mit denen ein Thread ausschließlich die Kontrolle über eine Variable oder einen Codeblock erlangen kann. Alle anderen Threads müssen warten, wenn sie ebenfalls versuchen, denselben Mutex abzurufen. Sobald der besitzende Thread den Mutex freigibt, kann ein anderer Thread den Mutex nacheinander abrufen.
Nachtrag (Juli 2016)
Die Synchronisation erfolgt für ein Objekt . Dies bedeutet, dass durch das Aufrufen einer synchronisierten Methode einer Klasse das
this
Objekt des Aufrufs gesperrt wird. Statisch synchronisierte Methoden sperren dasClass
Objekt selbst.Ebenso erfordert das Eingeben eines synchronisierten Blocks das Sperren des
this
Objekts der Methode.Dies bedeutet, dass eine synchronisierte Methode (oder ein synchronisierter Block) in mehreren Threads gleichzeitig ausgeführt werden kann, wenn sie unterschiedliche Objekte sperren. Es kann jedoch jeweils nur ein Thread eine synchronisierte Methode (oder einen synchronisierten Block) für ein bestimmtes einzelnes Objekt ausführen .
quelle
flüchtig:
volatile
ist ein Schlüsselwort.volatile
Erzwingt, dass alle Threads den neuesten Wert der Variablen aus dem Hauptspeicher anstelle des Caches abrufen. Für den Zugriff auf flüchtige Variablen ist keine Sperrung erforderlich. Alle Threads können gleichzeitig auf den Wert der flüchtigen Variablen zugreifen.Die Verwendung von
volatile
Variablen verringert das Risiko von Speicherkonsistenzfehlern, da bei jedem Schreiben in eine flüchtige Variable eine Beziehung hergestellt wird, die vor dem Lesen derselben Variablen auftritt.Dies bedeutet, dass Änderungen an einer
volatile
Variablen für andere Threads immer sichtbar sind . Darüber hinaus bedeutet dies, dass ein Thread beim Lesen einervolatile
Variablen nicht nur die letzte Änderung des Volatils sieht, sondern auch die Nebenwirkungen des Codes, der die Änderung ausgelöst hat .Verwendungszweck: Ein Thread ändert die Daten und andere Threads müssen den neuesten Datenwert lesen. Andere Threads ergreifen einige Maßnahmen, aktualisieren jedoch keine Daten .
AtomicXXX:
AtomicXXX
Klassen unterstützen die sperrenfreie threadsichere Programmierung einzelner Variablen. DieseAtomicXXX
Klassen (wieAtomicInteger
) beheben Speicherinkonsistenzfehler / Nebenwirkungen der Änderung flüchtiger Variablen, auf die in mehreren Threads zugegriffen wurde.Verwendungszweck: Mehrere Threads können Daten lesen und ändern.
synchronisiert:
synchronized
ist ein Schlüsselwort, das zum Schutz einer Methode oder eines Codeblocks verwendet wird. Das Synchronisieren der Methode hat zwei Auswirkungen:Erstens ist es nicht möglich, dass zwei Aufrufe von
synchronized
Methoden für dasselbe Objekt verschachtelt werden. Wenn ein Thread einesynchronized
Methode für ein Objekt ausführt ,synchronized
blockieren alle anderen Threads, die Methoden für denselben Objektblock aufrufen (Ausführung aussetzen), bis der erste Thread mit dem Objekt fertig ist.Zweitens wird beim
synchronized
Beenden einer Methode automatisch eine Vorher-Beziehung zu einem nachfolgenden Aufruf einersynchronized
Methode für dasselbe Objekt hergestellt. Dies garantiert, dass Änderungen am Status des Objekts für alle Threads sichtbar sind.Verwendungszweck: Mehrere Threads können Daten lesen und ändern. Ihre Geschäftslogik aktualisiert nicht nur die Daten, sondern führt auch atomare Operationen aus
AtomicXXX
entsprichtvolatile + synchronized
, obwohl die Implementierung unterschiedlich ist.AmtomicXXX
erweitertvolatile
Variablen +compareAndSet
Methoden, verwendet jedoch keine Synchronisation.Verwandte SE-Fragen:
Unterschied zwischen flüchtig und synchronisiert in Java
Volatile Boolean vs AtomicBoolean
Gute Artikel zum Lesen: (Der obige Inhalt stammt aus diesen Dokumentationsseiten)
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
quelle
Zwei Threads können nicht zweimal einen synchronisierten Block für dasselbe Objekt eingeben. Dies bedeutet, dass zwei Threads denselben Block für verschiedene Objekte eingeben können. Diese Verwirrung kann zu einem solchen Code führen.
Dies verhält sich nicht wie erwartet, da jedes Mal ein anderes Objekt gesperrt werden kann.
Ja. Es wird keine Verriegelung verwendet, um die Gewindesicherheit zu erreichen.
Wenn Sie genauer wissen möchten, wie sie funktionieren, können Sie den Code für sie lesen.
Die Atomklasse verwendet flüchtige Felder. Es gibt keinen Unterschied auf dem Gebiet. Der Unterschied besteht in den durchgeführten Operationen. Die Atomic-Klassen verwenden CompareAndSwap- oder CAS-Operationen.
Ich kann nur davon ausgehen, dass es sich um die Tatsache handelt, dass jede CPU ihre eigene zwischengespeicherte Speicheransicht hat, die sich von jeder anderen CPU unterscheiden kann. Um sicherzustellen, dass Ihre CPU eine konsistente Ansicht der Daten hat, müssen Sie Thread-Sicherheitstechniken verwenden.
Dies ist nur dann ein Problem, wenn der Speicher gemeinsam genutzt wird und mindestens ein Thread ihn aktualisiert.
quelle
Synchronized Vs Atomic Vs Volatile:
Bitte korrigieren Sie mich, wenn ich etwas verpasst habe.
quelle
Eine flüchtige + Synchronisation ist eine narrensichere Lösung für eine Operation (Anweisung), die vollständig atomar ist und mehrere Anweisungen an die CPU enthält.
Sprich zum Beispiel: volatile int i = 2; i ++, was nichts anderes ist als i = i + 1; Dies macht i nach der Ausführung dieser Anweisung zum Wert 3 im Speicher. Dies beinhaltet das Lesen des vorhandenen Werts aus dem Speicher für i (das ist 2), das Laden in das CPU-Akkumulatorregister und das Berechnen des vorhandenen Werts um eins (2 + 1 = 3 im Akkumulator) und das Zurückschreiben dieses inkrementierten Werts zurück in die Erinnerung. Diese Operationen sind nicht atomar genug, obwohl der Wert von i flüchtig ist. Wenn ich flüchtig bin, ist nur garantiert, dass ein einzelnes Lesen / Schreiben aus dem Speicher atomar ist und nicht mit MEHRFACH. Daher müssen wir auch um i ++ synchronisiert haben, damit es eine narrensichere atomare Anweisung ist. Denken Sie daran, dass eine Anweisung mehrere Anweisungen enthält.
Hoffe die Erklärung ist klar genug.
quelle
Der Java Volatile Modifier ist ein Beispiel für einen speziellen Mechanismus, der gewährleistet, dass die Kommunikation zwischen Threads erfolgt. Wenn ein Thread in eine flüchtige Variable schreibt und ein anderer Thread diesen Schreibvorgang sieht, informiert der erste Thread den zweiten über den gesamten Speicherinhalt, bis er den Schreibvorgang in diese flüchtige Variable ausgeführt hat.
Atomoperationen werden in einer einzigen Aufgabeneinheit ohne Störung durch andere Operationen ausgeführt. Atomare Operationen sind in Multithread-Umgebungen erforderlich, um Dateninkonsistenzen zu vermeiden.
quelle