Volatile Boolean vs AtomicBoolean


244

Was macht AtomicBoolean, was ein flüchtiger Boolescher Wert nicht erreichen kann?


16
Ich suchte nach einer differenzierteren Antwort auf: "Was sind die Grenzen der einzelnen?". Wenn beispielsweise ein Flag von einem Thread gesetzt und von einem oder mehreren anderen gelesen wird, ist AtomicBoolean nicht erforderlich. Wie ich jedoch bei diesen Antworten sehe, bringt AtomicBoolean nicht sperrende Operationen vom Typ CAS ins Spiel, wenn Threads eine Variable in mehreren Threads gemeinsam nutzen, die schreiben können und auf das Ergebnis ihrer Lesevorgänge reagieren. Eigentlich lerne ich hier ziemlich viel. Hoffentlich werden auch andere davon profitieren.
JeffV


Der flüchtige Boolesche Wert muss explizit synchronisiert werden, um die Race-Bedingungen zu bewältigen. Mit anderen Worten, ein Szenario wie die Aktualisierung der gemeinsam genutzten Ressource (Statusänderung) durch mehrere Threads, z. B. Inkrementierungs- / Dekrementierungszähler oder Flip-Boolescher Wert.
Sactiw

Antworten:


99

Sie sind einfach ganz anders. Betrachten Sie dieses Beispiel einer volatileGanzzahl:

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

Wenn zwei Threads die Funktion gleichzeitig aufrufen, ikann dies danach 5 sein, da der kompilierte Code diesem etwas ähnlich ist (außer Sie können nicht synchronisieren int):

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

Wenn eine Variable flüchtig ist, wird jeder atomare Zugriff darauf synchronisiert, aber es ist nicht immer offensichtlich, was tatsächlich als atomarer Zugriff qualifiziert ist. Mit einem Atomic*Objekt wird garantiert, dass jede Methode "atomar" ist.

Wenn Sie also ein AtomicIntegerund verwenden getAndAdd(int delta), können Sie sicher sein, dass das Ergebnis angezeigt wird 10. Auf die gleiche Weise können Sie sicher sein , dass zwei Threads gleichzeitig eine booleanVariable negieren. Mit a AtomicBooleankönnen Sie sicher sein, dass sie danach den ursprünglichen Wert hat. Mit a volatile booleankönnen Sie dies nicht.

Wenn also mehr als ein Thread ein Feld ändert, müssen Sie es atomar machen oder eine explizite Synchronisation verwenden.

Der Zweck von volatileist ein anderer. Betrachten Sie dieses Beispiel

volatile boolean stop = false;
void loop() {
    while (!stop) { ... }
}
void stop() { stop = true; }

Wenn ein Thread ausgeführt wird loop()und ein anderer Thread aufgerufen wird stop(), kann es zu einer Endlosschleife kommen, wenn Sie dies weglassen volatile, da der erste Thread möglicherweise den Wert von stop zwischenspeichert. Hier volatiledient das als Hinweis für den Compiler, bei Optimierungen etwas vorsichtiger zu sein.


84
-1: Sie geben Beispiele, erklären aber nicht wirklich den Unterschied zwischen einem flüchtigen und einem Atomicxxxx.
Jason S

70
Die Frage geht nicht um volatile. Die Frage ist etwa volatile booleanvs AtomicBoolean.
Dolmen

26
-1: Die speziell gestellte Frage zum Booleschen Wert ist ein eindeutiger Fall im Vergleich zu den anderen Datentypen und sollte direkt erläutert werden.
John Haager

8
@ sgp15 Es hat mit Synchronisation ab Java 5 zu tun.
Man of One Way

6
Wenn der boolesche Wert von vielen Threads gelesen, aber nur von einem Thread geschrieben wird, volatile booleanist dies ausreichend. Wenn es auch viele Schriftsteller gibt, brauchen Sie möglicherweise AtomicBoolean.
StvnBrkdll

263

Ich verwende flüchtige Felder, wenn dieses Feld NUR von seinem Eigentümer-Thread AKTUALISIERT wird und der Wert nur von anderen Threads gelesen wird. Sie können sich das als Publish / Subscribe-Szenario vorstellen, in dem es viele Beobachter, aber nur einen Publisher gibt. Wenn diese Beobachter jedoch eine Logik ausführen müssen, die auf dem Wert des Feldes basiert, und dann einen neuen Wert zurückschieben, dann gehe ich mit Atomic * vars oder Sperren oder synchronisierten Blöcken, was auch immer am besten zu mir passt. In vielen gleichzeitigen Szenarien läuft es darauf hinaus, den Wert abzurufen, mit einem anderen zu vergleichen und bei Bedarf zu aktualisieren, daher die in den Atomic * -Klassen vorhandenen Methoden compareAndSet und getAndSet.

In den JavaDocs des Pakets java.util.concurrent.atomic finden Sie eine Liste der Atomic-Klassen und eine hervorragende Erklärung ihrer Funktionsweise (Sie haben gerade erfahren, dass sie sperrenfrei sind, sodass sie einen Vorteil gegenüber Sperren oder synchronisierten Blöcken haben).


1
@ksl Ich denke, @teto möchte beschreiben, dass booleanwir wählen sollten , wenn nur ein Thread die Variable ändert volatile boolean.
Znlyj

2
Hervorragende Zusammenfassung.
Ravindra Babu

56

Sie können nicht tun compareAndSet, getAndSetals atomare Operation mit flüchtigen boolean (es sei denn natürlich synchronisieren Sie es).


6
Dies ist wahr, aber wäre dies nicht eine ziemlich seltene Voraussetzung für einen Booleschen Wert?
Robin

1
@Robin überlegt, ob Sie damit einen verzögerten Aufruf einer Initialisierungsmethode steuern können.
Ustaman Sangat

Eigentlich würde ich denken, dass dies einer der Hauptanwendungsfälle ist.
narr4jesus

42

AtomicBooleanhat Methoden, die ihre zusammengesetzten Operationen atomar und ohne Verwendung eines synchronizedBlocks ausführen . Auf der anderen Seite volatile booleankönnen zusammengesetzte Operationen nur ausgeführt werden, wenn dies innerhalb eines synchronizedBlocks erfolgt.

Die Memory-Effekte beim Lesen / Schreiben volatile booleansind identisch mit denen getbzw. setMethoden von AtomicBoolean.

Zum Beispiel führt die compareAndSetMethode atomar Folgendes aus (ohne synchronizedBlock):

if (value == expectedValue) {
    value = newValue;
    return true;
} else {
    return false;
}

Daher können Sie mit dieser compareAndSetMethode Code schreiben, der garantiert nur einmal ausgeführt wird, selbst wenn er von mehreren Threads aufgerufen wird. Beispielsweise:

final AtomicBoolean isJobDone = new AtomicBoolean(false);

...

if (isJobDone.compareAndSet(false, true)) {
    listener.notifyJobDone();
}

Es wird garantiert, dass der Listener nur einmal benachrichtigt wird (vorausgesetzt, kein anderer Thread setzt den AtomicBooleanBack falsewieder auf, nachdem er auf gesetzt wurde true).


14

volatileDas Schlüsselwort garantiert, dass die Beziehung zwischen Threads, die diese Variable gemeinsam nutzen, erfolgt. Es garantiert Ihnen nicht, dass sich zwei oder mehr Threads beim Zugriff auf diese boolesche Variable nicht gegenseitig unterbrechen.


14
Der boolesche Zugriff (wie beim primitiven Typ) ist in Java atomar. Sowohl liest als auch Zuweisungen. Kein anderer Thread "unterbricht" also boolesche Operationen.
Maciej Biłas

1
Es tut mir leid, aber wie beantwortet dies die Frage? Eine Atomic*Klasse umschließt ein volatileFeld.
Grau

Sind die CPU-Caches nicht der Hauptfaktor für die Einstellung der Volatilität? Um sicherzustellen, dass der gelesene Wert tatsächlich dem entspricht, auf den er zuletzt eingestellt wurde
jocull

8

Volatile Boolean vs AtomicBoolean

Die Atomic * -Klassen umschließen ein flüchtiges Grundelement desselben Typs. Aus der Quelle:

public class AtomicLong extends Number implements java.io.Serializable {
   ...
   private volatile long value;
   ...
   public final long get() {
       return value;
   }
   ...
   public final void set(long newValue) {
       value = newValue;
   }

Wenn Sie also nur ein Atomic * erhalten und einstellen, können Sie stattdessen auch ein flüchtiges Feld verwenden.

Was macht AtomicBoolean, was ein flüchtiger Boolescher Wert nicht erreichen kann?

Atomic * Klassen geben Ihnen Methoden , die erweiterte Funktionen bieten wie incrementAndGet(), compareAndSet()und andere , die mehrere Operationen (get / Zunahme / set, Test / set) ohne Verriegelung implementieren. Deshalb sind die Atomic * -Klassen so mächtig.

Wenn beispielsweise mehrere Threads den folgenden Code verwenden ++, gibt es Race-Bedingungen, da ++tatsächlich: get, inkrementieren und setzen.

private volatile value;
...
// race conditions here
value++;

Der folgende Code funktioniert jedoch in einer Multithread-Umgebung sicher ohne Sperren:

private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();

Es ist auch wichtig zu beachten, dass das Umschließen Ihres flüchtigen Felds mit der Atomic * -Klasse eine gute Möglichkeit ist, die kritische gemeinsam genutzte Ressource vom Standpunkt eines Objekts aus zu kapseln. Dies bedeutet, dass Entwickler nicht einfach mit dem Feld umgehen können, wenn es nicht gemeinsam genutzt wird, was möglicherweise Probleme mit einem Feld ++ verursacht. oder anderer Code, der Rennbedingungen einführt.


5

Wenn mehrere Threads auf Variablen auf Klassenebene zugreifen, kann jeder Thread eine Kopie dieser Variablen in seinem threadlokalen Cache behalten.

Wenn Sie die Variable flüchtig machen, wird verhindert, dass Threads die Kopie der Variablen im threadlokalen Cache behalten.

Atomvariablen sind unterschiedlich und ermöglichen eine atomare Änderung ihrer Werte.


4

Der boolesche primitive Typ ist atomar für Schreib- und Leseoperationen, flüchtig garantiert das Vorher-Geschehen-Prinzip. Wenn Sie also ein einfaches get () und set () benötigen, benötigen Sie den AtomicBoolean nicht.

Wenn Sie jedoch vor dem Festlegen des Werts einer Variablen eine Prüfung durchführen müssen, z. B. "Wenn true, dann auf false setzen", müssen Sie diese Operation auch atomar ausführen. Verwenden Sie in diesem Fall compareAndSet und andere von bereitgestellte Methoden AtomicBoolean, da Sie, wenn Sie versuchen, diese Logik mit einem flüchtigen Booleschen Wert zu implementieren, eine Synchronisierung benötigen, um sicherzustellen, dass sich der Wert zwischen get und set nicht geändert hat.


3

Erinnern Sie sich an das IDIOM -

READ - MODIFY- WRITE Dies können Sie mit volatile nicht erreichen


2
Kurz, knusprig und auf den Punkt. volatileFunktioniert nur in den Fällen, in denen der Eigentümer-Thread den Feldwert aktualisieren kann und die anderen Threads nur lesen können.
Chaklader Asfak Arefe

3

Wenn Sie nur einen Thread haben, der Ihren Booleschen Wert ändert, können Sie einen flüchtigen Booleschen Wert verwenden (normalerweise definieren Sie damit eine stopVariable, die in der Hauptschleife des Threads überprüft wird).

Wenn Sie jedoch mehrere Threads haben, die den Booleschen Wert ändern, sollten Sie einen verwenden AtomicBoolean. Andernfalls ist der folgende Code nicht sicher:

boolean r = !myVolatileBoolean;

Dieser Vorgang erfolgt in zwei Schritten:

  1. Der boolesche Wert wird gelesen.
  2. Der boolesche Wert wird geschrieben.

Wenn ein anderer Thread den Wert zwischen #1und ändert 2#, wird möglicherweise ein falsches Ergebnis angezeigt. AtomicBooleanMethoden vermeiden dieses Problem, indem sie Schritte #1und #2atomar ausführen.


-1

Beide haben das gleiche Konzept, aber im atomaren Booleschen Wert wird die Operation atomisiert, falls der CPU-Schalter dazwischen liegt.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.