Was macht AtomicBoolean, was ein flüchtiger Boolescher Wert nicht erreichen kann?
Was macht AtomicBoolean, was ein flüchtiger Boolescher Wert nicht erreichen kann?
Antworten:
Sie sind einfach ganz anders. Betrachten Sie dieses Beispiel einer volatile
Ganzzahl:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Wenn zwei Threads die Funktion gleichzeitig aufrufen, i
kann 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 AtomicInteger
und 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 boolean
Variable negieren. Mit a AtomicBoolean
können Sie sicher sein, dass sie danach den ursprünglichen Wert hat. Mit a volatile boolean
kö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 volatile
ist 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 volatile
dient das als Hinweis für den Compiler, bei Optimierungen etwas vorsichtiger zu sein.
volatile
. Die Frage ist etwa volatile boolean
vs AtomicBoolean
.
volatile boolean
ist dies ausreichend. Wenn es auch viele Schriftsteller gibt, brauchen Sie möglicherweise AtomicBoolean
.
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).
boolean
wir wählen sollten , wenn nur ein Thread die Variable ändert volatile boolean
.
Sie können nicht tun compareAndSet
, getAndSet
als atomare Operation mit flüchtigen boolean (es sei denn natürlich synchronisieren Sie es).
AtomicBoolean
hat Methoden, die ihre zusammengesetzten Operationen atomar und ohne Verwendung eines synchronized
Blocks ausführen . Auf der anderen Seite volatile boolean
können zusammengesetzte Operationen nur ausgeführt werden, wenn dies innerhalb eines synchronized
Blocks erfolgt.
Die Memory-Effekte beim Lesen / Schreiben volatile boolean
sind identisch mit denen get
bzw. set
Methoden von AtomicBoolean
.
Zum Beispiel führt die compareAndSet
Methode atomar Folgendes aus (ohne synchronized
Block):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Daher können Sie mit dieser compareAndSet
Methode 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 AtomicBoolean
Back false
wieder auf, nachdem er auf gesetzt wurde true
).
volatile
Das 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.
Atomic*
Klasse umschließt ein volatile
Feld.
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.
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.
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.
Erinnern Sie sich an das IDIOM -
READ - MODIFY- WRITE Dies können Sie mit volatile nicht erreichen
volatile
Funktioniert nur in den Fällen, in denen der Eigentümer-Thread den Feldwert aktualisieren kann und die anderen Threads nur lesen können.
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 stop
Variable, 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:
Wenn ein anderer Thread den Wert zwischen #1
und ändert 2#
, wird möglicherweise ein falsches Ergebnis angezeigt. AtomicBoolean
Methoden vermeiden dieses Problem, indem sie Schritte #1
und #2
atomar ausführen.
Beide haben das gleiche Konzept, aber im atomaren Booleschen Wert wird die Operation atomisiert, falls der CPU-Schalter dazwischen liegt.