web-dev-qa-db-ger.com

Was ist der Unterschied zwischen atomar/flüchtig/synchronisiert?

Wie arbeiten 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 volatile auf folgende Weise? 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 einen synchronisierten Block betreten können ... habe ich recht? Wenn dies zutrifft, wie funktioniert atomic.incrementAndGet() ohne synchronized? Und ist es fadensicher?

Und was ist der Unterschied zwischen internem Lesen und Schreiben zu flüchtigen Variablen/Atomvariablen? Ich habe in einem Artikel gelesen, dass der Thread eine lokale Kopie der Variablen hat - was ist das?

252
hardik

Sie fragen speziell, wie sie intern arbeiten sind, also hier sind Sie:

Keine Synchronisation

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Es liest im Wesentlichen den Wert aus dem Speicher, erhöht ihn und speichert ihn zurück. Dies funktioniert in Einzelthreads, aber heutzutage funktioniert es in Zeiten von Multi-Core-, Multi-CPU- und Multi-Level-Caches nicht mehr richtig. Zunächst wird die Race-Bedingung eingeführt (mehrere Threads können den Wert gleichzeitig lesen), aber auch Sichtprobleme. Der Wert wird möglicherweise nur im "local" CPU-Speicher (einiger Cache) gespeichert und ist für andere CPUs/Kerne (und damit - Threads) nicht sichtbar. Aus diesem Grund beziehen sich viele auf lokale Kopie einer Variablen in einem Thread. Es ist sehr unsicher. Betrachten Sie diesen beliebten, aber fehlerhaften Thread-Stopp-Code:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

public void pleaseStop() {
    stopped = true;
}

Fügen Sie der Variable volatilestopped hinzu und es funktioniert einwandfrei. Wenn ein anderer Thread die Variable stopped mit der pleaseStop()-Methode ändert, können Sie die Änderung sofort in der while(!stopped)-Schleife des Arbeitsthreads sehen. Übrigens, dies ist keine gute Möglichkeit, um einen Thread zu unterbrechen, siehe: So stoppen Sie einen Thread, der für immer ausgeführt wird, und zwar ohne Verwendung und Stoppen eines bestimmten Java-Threads .

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
  return counter.getAndIncrement();
}

Die AtomicInteger-Klasse verwendet CAS ( compare-and-swap ) Low-Level-CPU-Operationen (keine Synchronisierung erforderlich!). Sie können eine bestimmte Variable nur ändern, wenn der aktuelle Wert mit einem anderen Wert übereinstimmt (und erfolgreich zurückgegeben wird ). Wenn Sie getAndIncrement() ausführen, läuft es tatsächlich in einer Schleife (vereinfachte reale Implementierung):

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

Also im Grunde: lesen; Versuchen Sie, den inkrementierten Wert zu speichern. Wenn dies nicht erfolgreich ist (der Wert ist nicht mehr gleich current), lesen Sie und versuchen Sie es erneut. Die compareAndSet() ist in nativem Code (Assembly) implementiert.

volatile ohne Synchronisation

private volatile int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Dieser Code ist nicht korrekt. Es behebt das Sichtbarkeitsproblem (volatile stellt sicher, dass andere Threads Änderungen an counter sehen können), hat aber weiterhin eine Race-Bedingung. Dies wurde erklärt mehrmals: Pre-/Post-Inkrementierung ist nicht atomar.

Die einzige Nebenwirkung von volatile sind "Flushing" - Caches, sodass alle anderen Parteien die neueste Version der Daten sehen. Dies ist in den meisten Situationen zu streng. aus diesem Grund ist volatile kein Standard.

volatile ohne Synchronisation (2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

Das gleiche Problem wie oben, aber noch schlimmer, weil i nicht private ist. Der Rennzustand ist immer noch vorhanden. Warum ist das ein Problem? Wenn beispielsweise zwei Threads diesen Code gleichzeitig ausführen, könnte die Ausgabe + 5 oder + 10 sein. Die Änderung wird jedoch garantiert angezeigt.

Mehrere unabhängige synchronized

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

Überraschung ist auch dieser Code falsch. Tatsächlich ist es völlig falsch. Zunächst synchronisieren Sie sich auf i, das gerade geändert werden soll (außerdem ist i ein Primitiv. Ich denke, Sie synchronisieren sich auf eine temporäre Integer, die über Autoboxing erstellt wurde ...) Vollständig fehlerhaft. Sie könnten auch schreiben:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

Keine zwei Threads können denselben synchronized-Block mit derselben Sperre eingeben. In diesem Fall (und ähnlich in Ihrem Code) ändert sich das Sperrobjekt bei jeder Ausführung, sodass synchronized effektiv keine Auswirkungen hat.

Auch wenn Sie eine letzte Variable (oder this) für die Synchronisation verwendet haben, ist der Code immer noch falsch. Zwei Threads können zuerst i nach temp synchron lesen (mit demselben Wert lokal in temp), dann weist der erste einen neuen Wert für i (z. B. von 1 bis 6) zu und der andere tut dasselbe (von 1 bis 6). .

Die Synchronisation muss vom Lesen bis zum Zuweisen eines Wertes reichen. Ihre erste Synchronisation hat keine Auswirkung (das Lesen einer int ist atomar) und die zweite ebenfalls. Meiner Meinung nach sind dies die richtigen Formen:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

void incIBy5() {
  synchronized(this) {
    int temp = i;
    i = temp + 5;
  }
}
340

Wenn eine Variable als volatile deklariert wird, wirkt sich das Ändern des Werts sofort auf den tatsächlichen Speicher der Variablen aus. Der Compiler kann Verweise auf die Variable nicht wegoptimieren. Dies garantiert, dass bei Änderung der Variablen durch einen Thread alle anderen Threads den neuen Wert sofort sehen. (Dies ist nicht garantiert für nichtflüchtige Variablen.)

Das Deklarieren einer atomic - Variable garantiert, dass Operationen, die an der Variablen vorgenommen werden, auf atomare Weise ablaufen, d. H. Dass alle Teilschritte der Operation innerhalb des Threads ausgeführt werden, an dem sie ausgeführt werden und nicht durch andere Threads unterbrochen werden. Zum Beispiel erfordert eine Inkrementierungs- und Testoperation, dass die Variable erhöht wird und dann mit einem anderen Wert verglichen wird. Eine atomare Operation garantiert, dass diese beiden Schritte so ausgeführt werden, als ob es sich um eine einzelne unteilbare/ununterbrechbare Operation handeln würde.

Beim Synchronisieren Bei allen Zugriffen auf eine Variable kann jeweils nur ein einzelner Thread auf die Variable zugreifen, und alle anderen Threads müssen warten, bis der zugreifende Thread den Zugriff auf die Variable freigibt. 

Der synchronisierte Zugriff ähnelt dem atomaren Zugriff, aber die atomaren Operationen werden im Allgemeinen auf einer niedrigeren Programmierebene implementiert. Es ist auch durchaus möglich, nur einige Zugriffe auf eine Variable zu synchronisieren und zu gestatten, dass andere Zugriffe nicht synchronisiert werden (z. B. alle Schreibvorgänge mit einer Variablen, aber keinen der Lesevorgänge von ihr synchronisieren).

Atomarität, Synchronisation und Volatilität sind unabhängige Attribute, werden jedoch in der Regel in Kombination verwendet, um eine korrekte Thread-Kooperation für den Zugriff auf Variablen zu erzwingen.

Nachtrag (April 2016)

Der synchronisierte Zugriff auf eine Variable wird normalerweise mit monitor oder semaphore implementiert. Hierbei handelt es sich um Low-Level-Mechanismen mutex (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 auch versuchen, denselben Mutex zu erhalten. Sobald der besitzende Thread den Mutex freigibt, kann ein anderer Thread den Mutex der Reihe nach erwerben.

Nachtrag (Juli 2016)

Die Synchronisation erfolgt für ein object. Das bedeutet, dass das Aufrufen einer synchronisierten Methode einer Klasse das this-Objekt des Aufrufs sperrt. Statisch synchronisierte Methoden sperren das Class-Objekt selbst.

Die Eingabe eines synchronisierten Blocks erfordert ebenfalls 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 für different -Objekte gesperrt sind, aber nur ein Thread kann jeweils eine synchronisierte Methode (oder einen synchronen Block) gleichzeitig ausführen single Objekt gegeben.

49
David R Tribble

flüchtig:

volatile ist ein Schlüsselwort. volatile zwingt alle Threads, den neuesten Wert der Variablen aus dem Hauptspeicher statt aus dem Cache abzurufen. 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 Schreibvorgang in eine flüchtige Variable eine Vorfall-Beziehung mit nachfolgenden Lesevorgängen derselben Variablen hergestellt wird. 

Dies bedeutet, dass Änderungen an einer Variable volatile für andere Threads immer sichtbar sind. Darüber hinaus bedeutet dies auch, dass wenn ein Thread eine Variable volatile liest, sieht er nicht nur die letzte Änderung an der Volatilität, sondern auch die Nebenwirkungen des Codes, der zu der Änderung geführt hat.

Wann zu verwenden: Ein Thread ändert die Daten und andere Threads müssen den neuesten Wert der Daten lesen. Andere Threads müssen etwas unternehmen, aber sie aktualisieren keine Daten.

AtomicXXX:

AtomicXXX-Klassen unterstützen die lock-freie Thread-sichere Programmierung für einzelne Variablen. Diese AtomicXXX-Klassen (wie AtomicInteger) beheben Speicherinkonsistenzfehler/Nebeneffekte der Änderung flüchtiger Variablen, auf die in mehreren Threads zugegriffen wurde. 

Wann verwenden: Mehrere Threads können Daten lesen und ändern. 

synchronisiert:

synchronized ist ein Schlüsselwort, mit dem eine Methode oder ein Codeblock geschützt wird. Wenn Sie die Methode synchronisieren, hat dies zwei Auswirkungen:

  1. Erstens ist es nicht möglich, zwei Aufrufe von synchronized-Methoden für dasselbe Objekt zu verschachteln. Wenn ein Thread eine synchronized-Methode für ein Objekt ausführt, werden alle anderen Threads, die synchronized-Methoden für dasselbe Objekt aufrufen, blockiert (Suspend-Ausführung), bis der erste Thread mit dem Objekt fertig ist.

  2. Zweitens, wenn eine synchronized-Methode beendet wird, wird automatisch eine Ereignis-vor-Beziehung mit einem nachfolgenden Aufruf einer synchronized-Methode für dasselbe Objekt hergestellt. Dies garantiert, dass Änderungen des Status des Objekts für alle Threads sichtbar sind.

Wann verwenden: Mehrere Threads können Daten lesen und ändern. Ihre Geschäftslogik aktualisiert nicht nur die Daten, sondern führt auch atomare Operationen aus. 

AtomicXXX entspricht volatile + synchronized, auch wenn die Implementierung anders ist. AmtomicXXX erweitert volatile variables + 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 ist diesen Dokumentationsseiten entnommen)

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

15
Ravindra babu

Ich weiß, dass zwei Threads nicht gleichzeitig in den Block "Synchronisieren" eintreten können

Zwei Threads können einen synchronisierten Block für dasselbe Objekt nicht zweimal eingeben. Dies bedeutet, dass zwei Threads denselben Block für verschiedene Objekte eingeben können. Diese Verwirrung kann zu Code wie diesem führen.

private Integer i = 0;

synchronized(i) {
   i++;
}

Dies wird sich nicht wie erwartet verhalten, da jedes Mal ein anderes Objekt gesperrt werden kann.

wenn dies wahr ist, wie funktioniert dieses atomic.incrementAndGet () ohne Synchronize? und ist Thread sicher?

ja. Es verwendet keine Verriegelung, um die Gewindesicherheit zu erreichen.

Wenn Sie wissen möchten, wie sie genauer arbeiten, können Sie den Code für sie lesen.

Und was ist der Unterschied zwischen internem Lesen und Schreiben in volatile Variable/Atomic Variable?

Atomic-Klasse verwendet flüchtigeFelder. Es gibt keinen Unterschied im Feld. Der Unterschied besteht in den ausgeführten Operationen. Die Atomic-Klassen verwenden CompareAndSwap- oder CAS-Operationen.

ich habe in einem Artikel gelesen, dass der Thread eine lokale Kopie der Variablen hat. Was ist das?

Ich kann nur davon ausgehen, dass jede CPU ihre eigene zwischengespeicherte Ansicht des Speichers hat, die sich von jeder anderen CPU unterscheiden kann. Um sicherzustellen, dass Ihre CPU eine konsistente Sicht auf die Daten hat, müssen Sie Thread-Sicherheitstechniken verwenden. 

Dies ist nur ein Problem, wenn der Speicher gemeinsam genutzt wird. Mindestens ein Thread aktualisiert ihn.

5
Peter Lawrey

Eine flüchtige + Synchronisation ist eine narrensichere Lösung für eine vollständig atomare Operation (Anweisung), die mehrere Anweisungen an die CPU enthält.

Sagen Sie zum Beispiel: flüchtig int i = 2; i ++, das ist nichts als i = i + 1; Dies macht i als Wert 3 im Speicher nach der Ausführung dieser Anweisung. Dies umfasst das Lesen des vorhandenen Werts aus dem Speicher für i (was 2 ist), das Laden in das CPU-Akkumulatorregister und die Berechnung durch Erhöhen der vorhandener Wert mit eins (2 + 1 = 3 im Akku) und schreiben diesen inkrementierten Wert zurück in den Speicher. Diese Operationen sind nicht atomar genug, obwohl der Wert von i volatil ist. Da ich flüchtig bin, ist nur gewährleistet, dass ein SINGLE-Lese-/Schreibzugriff aus dem Speicher atomar ist und nicht bei MULTIPLE. Daher müssen wir uns auch in der Umgebung von i ++ synchronisiert haben, damit die atomare Aussage absolut sicher bleibt. Denken Sie daran, dass eine Anweisung mehrere Anweisungen enthält.

Hoffe die Erklärung ist klar genug.

1
Thomas Mathew

Synchronized Vs Atomic Vs Volatile:
1. Volatile und Atomic gilt nur für Variable, While Synchronized für Methode .
2. Volatile sorgen für Sichtbarkeit und nicht für Atomizität/Konsistenz des Objekts, während andere für Sichtbarkeit und Atomizität sorgen .
3. Speichern von flüchtigen Variablen in RAM, und der Zugriff ist schneller, aber wir können keine Thread-Sicherheit oder -Synchronisation ohne synchronisiertes Schlüsselwort .
4. Synchronisiert als synchronisierter Block oder synchronisiertes Verfahren implementiert, beide jedoch nicht. Mit Hilfe eines synchronisierten Schlüsselworts können wir sichere mehrzeilige Codes einfädeln.
5. Synchronized kann dasselbe Klassenobjekt oder ein anderes Klassenobjekt sperren, während beide nicht .
Bitte korrigieren Sie mich, falls mir etwas fehlt.

1
Ajay Gupta

Der Java volatile - Modifier ist ein Beispiel für einen speziellen Mechanismus, um die Kommunikation zwischen Threads zu gewährleisten. Wenn ein Thread in eine flüchtige Variable schreibt und ein anderer Thread das Schreiben sieht, teilt der erste Thread dem zweiten Thread den gesamten Inhalt des Speichers mit, bis er in diese flüchtige Variable geschrieben hat.

Atomische Operationen werden in einer einzigen Taskeinheit ausgeführt, ohne dass andere Operationen sie stören. In Multithread-Umgebungen sind atomare Operationen erforderlich, um Dateninkonsistenzen zu vermeiden.

0
Sai prateek