Wann muss ich AtomicBoolean in Java verwenden?

Antworten:

244

Wenn mehrere Threads den Booleschen Wert überprüfen und ändern müssen. Beispielsweise:

if (!initialized) {
   initialize();
   initialized = true;
}

Dies ist nicht threadsicher. Sie können das Problem beheben, indem Sie Folgendes verwenden AtomicBoolean:

if (atomicInitialized.compareAndSet(false, true)) {
    initialize();
}
Bozho
quelle
51
Es sieht nicht wie ein Beispiel aus der Praxis aus - andere Threads können sehen, truewenn sie initialize()noch nicht abgeschlossen sind. Es funktioniert also nur, wenn andere Threads sich nicht um die Fertigstellung kümmern initialize().
Axtavt
6
@axtavt: Ich denke, es ist ein perfekt gültiges Beispiel aus der Praxis, wenn initializedes einfach verwendet wird, um sicherzustellen, dass nur ein Thread die initialize()Methode aufruft . Offensichtlich initializedwahr zu sein bedeutet nicht , dass die Initialisierung auf jeden Fall in diesem Fall abgeschlossen, so vielleicht ein etwas anderer Begriff wäre besser hier. Auch hier kommt es darauf an, wofür es verwendet wird.
ColinD
14
Sie würden 2 Boolesche Werte für initStarted und initCompleted benötigen, dann setzt der erste Thread initStarted und ruft initialise () auf, der Rest wartet, bis initCompleted true ist.
Martin
3
@Bozho - Lesen und Schreiben in boolesche Felder sind atomar, oder?, Jetzt gibt mir flüchtig den neuesten Wert des booleschen Feldes. Also wäre das nicht volatile booleandasselbe wie AtomicBoolean?
TheLostMind
2
@ Martin: Es gibt keine direkte Möglichkeit, darauf zu warten, dass ein Boolescher Wert wahr wird. Sie benötigen zusätzliche Mechanismen. Der sinnvollste Ansatz ist die Verwendung eines synchronizedBlocks. In diesem Fall benötigen Sie keinen AtomicBoolean, nur noch einen volatile boolean. (stellt if(! this.initialized) { synchronized(this) { if(! this.initialized) { initialize(); this.initialized = true; } } }sicher, dass nur ein Thread aufruft initializeund dass alle anderen Threads darauf warten, sofern dies initializedmarkiert ist volatile.)
Ruakh
52

Hier sind die Notizen (aus dem Buch von Brian Goetz ), die ich gemacht habe und die Ihnen vielleicht helfen könnten

AtomicXXX-Klassen

  • Bereitstellung einer nicht blockierenden Compare-And-Swap-Implementierung

  • Nutzt die Unterstützung durch Hardware (die CMPXCHG-Anweisung unter Intel). Wenn viele Threads durch Ihren Code laufen, der diese atomare Parallelitäts-API verwendet, werden sie viel besser skaliert als Code, der Monitore / Synchronisation auf Objektebene verwendet. Da die Synchronisationsmechanismen von Java den Code warten lassen, wenn viele Threads durch Ihre kritischen Abschnitte laufen, wird eine erhebliche Menge an CPU-Zeit für die Verwaltung des Synchronisationsmechanismus selbst aufgewendet (Warten, Benachrichtigen usw.). Da die neue API Konstrukte auf Hardwareebene (atomare Variablen) und warte- und sperrfreie Algorithmen verwendet, um die Thread-Sicherheit zu implementieren, wird viel mehr CPU-Zeit für "Aufgaben" aufgewendet als für die Verwaltung der Synchronisation.

  • Sie bieten nicht nur einen besseren Durchsatz, sondern auch einen größeren Widerstand gegen Lebendigkeitsprobleme wie Deadlock und Prioritätsinversion.

Aravind Yarram
quelle
34

Es gibt zwei Hauptgründe, warum Sie einen atomaren Booleschen Wert verwenden können. Zunächst ist es veränderbar. Sie können es als Referenz übergeben und beispielsweise den Wert ändern, der dem Booleschen Wert selbst zugeordnet ist.

public final class MyThreadSafeClass{

    private AtomicBoolean myBoolean = new AtomicBoolean(false);
    private SomeThreadSafeObject someObject = new SomeThreadSafeObject();

    public boolean doSomething(){
         someObject.doSomeWork(myBoolean);
         return myBoolean.get(); //will return true
    }
}

und in der someObject-Klasse

public final class SomeThreadSafeObject{
    public void doSomeWork(AtomicBoolean b){
        b.set(true);
    }
}

Noch wichtiger ist jedoch, dass der Thread sicher ist und Entwicklern, die die Klasse verwalten, anzeigen kann, dass erwartet wird, dass diese Variable geändert und aus mehreren Threads gelesen wird. Wenn Sie keinen AtomicBoolean verwenden, müssen Sie die von Ihnen verwendete boolesche Variable synchronisieren, indem Sie sie als flüchtig deklarieren oder um das Lesen und Schreiben des Felds herum synchronisieren.

John Vint
quelle
4
Aus Liebe zu Gott sollte dies nur die Veränderlichkeit des Objekts selbst zeigen. Ich habe das speziell zu Demonstrationszwecken geschrieben.
John Vint
Und weiter, wenn das ALLES war, was passiert ist, dann wird es immer wahr sein
John Vint
Das beweist nicht, ob es threadsicher ist oder nicht. Ich kann meine Codeausschnitte fertigstellen, um die Klasse sehr threadsicher zu machen, aber das bringt meinen Standpunkt nur zum Erliegen.
John Vint
1
Ich denke, dass nur Volatile nicht ausreicht. Stellen Sie sich eine Situation vor, in der zwei Threads, die denselben Wert direkt aus dem Hauptspeicher lesen und schreiben, keine Synchronisierung zwischen diesen Threads durchführen - es können Probleme mit der Parallelität auftreten.
Shay Tsadok
1
Sie haben Recht, es würde nicht ausreichen, um Atomic Set-Operationen zu überprüfen, obwohl es nicht genug Kontext vom OP gab, um diese Annahme zu treffen. Zu sagen, dass Volatilität möglicherweise nicht ausreicht, ist natürlich immer abhängig von der jeweiligen Situation.
John Vint
18

Die AtomicBooleanKlasse gibt Ihnen einen booleschen Wert, den Sie atomar aktualisieren können. Verwenden Sie diese Option, wenn mehrere Threads auf eine boolesche Variable zugreifen.

In der Paketübersicht java.util.concurrent.atomic finden Sie eine allgemeine Beschreibung dessen, was die Klassen in diesem Paket tun und wann sie verwendet werden sollen. Ich würde auch das Buch Java Concurrency in Practice von Brian Goetz empfehlen .

Cameron Skinner
quelle
5

Auszug aus der Paketbeschreibung

Paket java.util.concurrent.atomic Beschreibung: Ein kleines Toolkit von Klassen, die eine sperrenfreie threadsichere Programmierung für einzelne Variablen unterstützen. [...]

Die Spezifikationen dieser Methoden ermöglichen es Implementierungen, effiziente atomare Anweisungen auf Maschinenebene zu verwenden, die auf modernen Prozessoren verfügbar sind. [...]

Instanzen der Klassen AtomicBoolean, AtomicInteger, AtomicLong und AtomicReference bieten jeweils Zugriff und Aktualisierungen auf eine einzelne Variable des entsprechenden Typs. [...]

Die Memory-Effekte für Zugriffe und Aktualisierungen von Atomics folgen im Allgemeinen den Regeln für flüchtige Stoffe:

  • get hat die Memory-Effekte des Lesens einer flüchtigen Variablen.
  • set hat die Speichereffekte des Schreibens (Zuweisens) einer flüchtigen Variablen.
  • schwachCompareAndSet liest eine Variable atomar und schreibt sie bedingt, ist in Bezug auf andere Speicheroperationen für diese Variable geordnet, wirkt aber ansonsten wie eine gewöhnliche nichtflüchtige Speicheroperation.
  • compareAndSet und alle anderen Lese- und Aktualisierungsvorgänge wie getAndIncrement haben die Speichereffekte beim Lesen und Schreiben flüchtiger Variablen.
OscarRyz
quelle