Wann wird AtomicReference in Java verwendet?

Wann verwenden wir AtomicReference?

Ist es erforderlich, Objekte in allen Multithread-Programmen zu erstellen?

Geben Sie ein einfaches Beispiel an, in dem AtomicReference verwendet werden soll.

Die atomare Referenz sollte in einer Einstellung verwendet werden, in der Sie einfache atomare (dh threadsichere , nicht-triviale) Operationen an einer Referenz durchführen müssen, für die die monitorbasierte Synchronisation nicht geeignet ist. Angenommen, Sie möchten überprüfen, ob ein bestimmtes Feld nur dann angezeigt wird, wenn der Status des Objekts so bleibt, wie Sie es zuletzt überprüft haben:

AtomicReference cache = new AtomicReference(); Object cachedValue = new Object(); cache.set(cachedValue); //... time passes ... Object cachedValueToUpdate = cache.get(); //... do some work to transform cachedValueToUpdate into a new version Object newValue = someFunctionOfOld(cachedValueToUpdate); boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate); 

Aufgrund der Semantik des atomaren Verweises können Sie dies auch dann tun, wenn das cache Objekt unter Threads geteilt wird, ohne dass die synchronized . Im Allgemeinen ist es besser, wenn Sie Synchronisierer oder das java.util.concurrent Framework anstelle von bloßem Atomic* sei denn, Sie wissen, was Sie tun.

Zwei ausgezeichnete Dead-Tree-Referenzen, die Sie mit diesem Thema vertraut machen werden:

  • Herlihys exzellente Art der Multiprozessor-Programmierung
  • Java Concurrency in der Praxis

Beachten Sie, dass die Referenzzuweisung (ie = ) selbst atomar ist (ich weiß nicht, ob das schon immer so war) (das Aktualisieren primitiver 64-Bit-Typen wie long oder double möglicherweise nicht atomar; das Aktualisieren einer Referenz ist jedoch immer atomar, auch wenn es ist 64 Bit) ohne explizit ein Atomic* .
Siehe die Java-Sprachspezifikation 3ed, Abschnitt 17.7 .

Ein atomarer Verweis ist ideal, wenn Sie den Status eines unveränderlichen Objekts zwischen mehreren Threads teilen und ändern müssen. Das ist eine sehr dichte Aussage, also werde ich es ein bisschen abbrechen.

Erstens ist ein unveränderliches Objekt ein Objekt, das nach der Konstruktion tatsächlich nicht geändert wird. Die Methoden eines unveränderlichen Objekts geben häufig neue Instanzen derselben class zurück. Einige Beispiele umfassen die Wrapper-classn Long und Double sowie String, um nur einige zu nennen. (Laut Programming Concurrency auf der JVM sind unveränderliche Objekte ein kritischer Teil der modernen Parallelität).

Weiter, warum AtomicReference ist besser als ein flüchtiges Objekt für die Freigabe dieses freigegebenen Werts. Ein einfaches Codebeispiel zeigt den Unterschied.

 volatile String sharedValue; static final Object lock=new Object(); void modifyString(){ synchronized(lock){ sharedValue=sharedValue+"something to add"; } } 

Jedes Mal, wenn Sie die Zeichenfolge, die von diesem flüchtigen Feld referenziert wird, basierend auf dem aktuellen Wert ändern möchten, müssen Sie zuerst eine Sperre für dieses Objekt abrufen. Dies verhindert, dass während der Zwischenzeit ein anderer Thread eingeht, und ändert den Wert in der Mitte der neuen String-Verkettung. Dann, wenn der Thread wieder aufgenommen wird, überstülpen Sie die Arbeit des anderen Threads. Aber ehrlich gesagt wird dieser Code funktionieren, er sieht sauber aus und würde die meisten Menschen glücklich machen.

Kleines Problem. Es ist langsam. Vor allem, wenn es eine Menge Streit um dieses Lock-Objekt gibt. Das liegt daran, dass die meisten Sperren einen Betriebssystem-Systemaufruf erfordern und Ihr Thread wird blockiert und der Kontext wird aus der CPU ausgeschalten, um anderen processen Platz zu machen.

Die andere Option ist die Verwendung eines AtomicRefrence.

 public static AtomicReference shared = new AtomicReference<>(); String init="Inital Value"; shared.set(init); //now we will modify that value boolean success=false; while(!success){ String prevValue=shared.get(); // do all the work you need to String newValue=shared.get()+"lets add something"; // Compare and set success=shared.compareAndSet(prevValue,newValue); } 

Nun, warum ist das besser? Ehrlich gesagt ist dieser Code etwas weniger sauber als zuvor. Aber es gibt etwas wirklich Wichtiges, das in AtomicRefrence unter der Haube passiert, und das ist Vergleichen und Tauschen. Es ist ein einzelner CPU-Befehl, kein OS-Aufruf, der den Wechsel ermöglicht. Das ist eine einzelne statement auf der CPU. Und da es keine Sperren gibt, gibt es keinen Kontextwechsel für den Fall, dass die Sperre ausgeübt wird, was noch mehr Zeit spart!

Der Haken bei AtomicReferences ist, dass hier kein .equals () – Aufruf, sondern ein == -Vergleich für den erwarteten Wert verwendet wird. Stellen Sie also sicher, dass das erwartete Objekt das tatsächliche Objekt ist, das von get in the loop zurückgegeben wurde.

Hier ist ein Anwendungsfall für AtomicReference:

Betrachten Sie diese class, die als Zahlenbereich fungiert, und verwenden Sie einzelne AtmomicInteger-Variablen, um die unteren und oberen Zahlengrenzen zu erhalten.

 public class NumberRange { // INVARIANT: lower < = upper private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); public void setLower(int i) { // Warning -- unsafe check-then-act if (i > upper.get()) throw new IllegalArgumentException( "can't set lower to " + i + " > upper"); lower.set(i); } public void setUpper(int i) { // Warning -- unsafe check-then-act if (i < lower.get()) throw new IllegalArgumentException( "can't set upper to " + i + " < lower"); upper.set(i); } public boolean isInRange(int i) { return (i >= lower.get() && i < = upper.get()); } } 

Sowohl setLower als auch setUpper sind check-then-act-Sequenzen, aber sie verwenden keine ausreichende Sperre, um sie atomar zu machen. Wenn der Nummernbereich (0, 10) gilt und ein Thread setLower (5) aufruft, während ein anderer Thread setUpper (4) aufruft, werden beide mit einem unglücklichen Timing die Prüfungen in den Setter bestehen und beide Modifikationen werden angewendet. Das Ergebnis ist, dass der Bereich nun (5, 4) einen ungültigen Zustand aufweist. Während die zugrundeliegenden AtomicInteger Thread-sicher sind, ist die Composite-class nicht. Dies kann behoben werden, indem eine AtomicReference verwendet wird, anstatt einzelne AtomicIntegers für obere und untere Grenzen zu verwenden.

 public class CasNumberRange { //Immutable private static class IntPair { final int lower; // Invariant: lower < = upper final int upper; ... } private final AtomicReference values = new AtomicReference(new IntPair(0, 0)); public int getLower() { return values.get().lower; } public int getUpper() { return values.get().upper; } public void setLower(int i) { while (true) { IntPair oldv = values.get(); if (i > oldv.upper) throw new IllegalArgumentException( "Can't set lower to " + i + " > upper"); IntPair newv = new IntPair(i, oldv.upper); if (values.compareAndSet(oldv, newv)) return; } } // similarly for setUpper } 

Sie können AtomicReference verwenden, wenn Sie optimistische Sperren anwenden. Sie haben ein gemeinsames Objekt und möchten es von mehr als einem Thread ändern.

  1. Sie können eine Kopie des gemeinsamen Objekts erstellen
  2. Ändern Sie das gemeinsame Objekt
  3. Sie müssen überprüfen, ob das gemeinsame Objekt immer noch das gleiche ist – wenn ja, dann aktualisieren Sie mit der Referenz der modifizierten Kopie.

Wie andere Threads es möglicherweise geändert haben und / kann zwischen diesen 2 Schritten ändern. Sie müssen es in einer atomaren Operation tun. Hier hilft AtomicReference

Ich werde nicht viel reden. Meine geschätzten Freunde haben bereits wertvolle Beiträge geleistet. Der vollständige laufende Code im letzten Blog sollte jegliche Verwirrung beseitigen. Es geht um einen Kinobesuch, der ein kleines Programm im Multi-Thread-Szenario bucht.

Einige wichtige elementare Fakten sind wie folgt. 1> Unterschiedliche Threads können nur für Beispiel- und statische Member-Variablen im Heap-Space konkurrieren. 2> Volatiles Lesen oder Schreiben sind vollständig atomar und serialisiert / passiert vor und nur aus dem Speicher. Indem ich das sage, meine ich, dass jedes Lesen dem vorherigen Schreiben im Gedächtnis folgt. Und jeder Schreibvorgang folgt dem vorherigen Lesen aus dem Speicher. Daher wird jeder Thread, der mit einem Volatile arbeitet, immer den aktuellsten Wert sehen. AtomicReference verwendet diese Eigenschaft von volatile.

Im Folgenden finden Sie einige der Quellcode von AtomicReference. AtomicReference verweist auf eine Objektreferenz. Dieser Verweis ist eine volatile Membervariable in der AtomicReference-Instanz wie folgt.

 private volatile V value; 

get () gibt einfach den letzten Wert der Variablen zurück (so wie es flüchtige Personen tun, geschieht “vor”).

 public final V get() 

Folgendes ist die wichtigste Methode der AtomicReference.

 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } 

Die compareAndSet-Methode (expect, update) ruft die compareAndSwapObject () -Methode der unsicheren class von Java auf. Dieser Methodenaufruf von unsafe ruft den systemeigenen Aufruf auf, der eine einzelne statement an den processor aufruft. “expect” und “update” jede Referenz auf ein Objekt.

Wenn und nur wenn die AtomicReference-Instanzelementvariable “value” auf dasselbe Objekt verweist, wird “expect” dieser Instanzvariable jetzt “update” zugewiesen, und “true” wird zurückgegeben. Sonst wird falsch zurückgegeben. Das Ganze ist atomar erledigt. Kein anderer Thread kann dazwischen abfangen. Da es sich um einen Ein-processor-Betrieb handelt (Magie der modernen Computerarchitektur), ist es oft schneller als ein synchronisierter Block. Denken Sie jedoch daran, dass AtomicReference nicht hilft, wenn mehrere Variablen atomar aktualisiert werden müssen.

Ich möchte einen vollwertigen Running-Code hinzufügen, der in Eclipse ausgeführt werden kann. Es würde viele Verwirrung beseitigen. Hier versuchen 22 Benutzer (MyTh-Threads) 20 Plätze zu buchen. Es folgt das Code-Snippet gefolgt von dem vollständigen Code.

Codeschnipsel, in dem 22 Benutzer 20 Plätze buchen möchten.

 for (int i = 0; i < 20; i++) {// 20 seats seats.add(new AtomicReference()); } Thread[] ths = new Thread[22];// 22 users for (int i = 0; i < ths.length; i++) { ths[i] = new MyTh(seats, i); ths[i].start(); } 

Im Folgenden ist der vollständige Ausführungscode aufgeführt.

 import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; public class Solution { static List> seats;// Movie seats numbered as per // list index public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub seats = new ArrayList<>(); for (int i = 0; i < 20; i++) {// 20 seats seats.add(new AtomicReference()); } Thread[] ths = new Thread[22];// 22 users for (int i = 0; i < ths.length; i++) { ths[i] = new MyTh(seats, i); ths[i].start(); } for (Thread t : ths) { t.join(); } for (AtomicReference seat : seats) { System.out.print(" " + seat.get()); } } /** * id is the id of the user * * @author sankbane * */ static class MyTh extends Thread {// each thread is a user static AtomicInteger full = new AtomicInteger(0); List> l;//seats int id;//id of the users int seats; public MyTh(List> list, int userId) { l = list; this.id = userId; seats = list.size(); } @Override public void run() { boolean reserved = false; try { while (!reserved && full.get() < seats) { Thread.sleep(50); int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes // seats // AtomicReference el = l.get(r); reserved = el.compareAndSet(null, id);// null means no user // has reserved this // seat if (reserved) full.getAndIncrement(); } if (!reserved && full.get() == seats) System.out.println("user " + id + " did not get a seat"); } catch (InterruptedException ie) { // log it } } } } 

Wann verwenden wir AtomicReference?

AtomicReference ist eine flexible Möglichkeit, den Variablenwert ohne Synchronisation zu aktualisieren.

AtomicReference unterstützt die AtomicReference Thread-sichere Programmierung einzelner Variablen.

Es gibt mehrere Möglichkeiten, um Thread-Sicherheit mit gleichzeitigem High-Level-API zu erreichen. Atomare Variablen sind eine der vielen Optionen.

Lock Objekte unterstützen Sperr-Idiome, die viele gleichzeitige Anwendungen vereinfachen.

Executors definieren eine High-Level-API zum Starten und Verwalten von Threads. Executor-Implementierungen, die von java.util.concurrent bereitgestellt werden, stellen eine Thread-Pool-Verwaltung bereit, die für große Anwendungen geeignet ist.

Gleichzeitige Sammlungen erleichtern die Verwaltung umfangreicher Datensammlungen und können die Notwendigkeit der Synchronisierung erheblich reduzieren.

Atomare Variablen verfügen über functionen, die die Synchronisierung minimieren und Speicherkonsistenzerrors vermeiden helfen.

Geben Sie ein einfaches Beispiel an, in dem AtomicReference verwendet werden soll.

Beispielcode mit AtomicReference :

 String initialReference = "value 1"; AtomicReference someRef = new AtomicReference(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged); 

Ist es erforderlich, Objekte in allen Multithread-Programmen zu erstellen?

Sie müssen AtomicReference in allen Multithread-Programmen verwenden.

Wenn Sie eine einzelne Variable schützen möchten, verwenden Sie AtomicReference . Wenn Sie einen Codeblock schützen möchten, verwenden Sie andere Konstrukte wie Lock / synchronized usw.

Hier ist ein sehr einfacher Anwendungsfall und hat nichts mit Thread-Sicherheit zu tun.

Um ein Objekt zwischen Lambda-Aufrufen zu teilen, ist die AtomicReference eine Option :

 public void doSomethingUsingLambdas() { AtomicReference yourObjectRef = new AtomicReference<>(); soSomethingThatTakesALambda(() -> { yourObjectRef.set(youObject); }); soSomethingElseThatTakesALambda(() -> { YourObject yourObject = yourObjectRef.get(); }); } 

Ich sage nicht, das ist gutes Design oder irgendetwas (es ist nur ein triviales Beispiel), aber wenn Sie den Fall haben, wo Sie ein Objekt zwischen Lambda- AtomicReference teilen müssen, ist die AtomicReference eine Option.

Tatsächlich können Sie jedes Objekt verwenden, das eine Referenz enthält, sogar eine Collection, die nur ein Objekt enthält. Die AtomicReference passt jedoch perfekt.

Ein anderes einfaches Beispiel besteht darin, eine Safe-Thread-Änderung in einem Sitzungsobjekt vorzunehmen.

 public PlayerScore getHighScore() { ServletContext ctx = getServletConfig().getServletContext(); AtomicReference holder = (AtomicReference) ctx.getAttribute("highScore"); return holder.get(); } public void updateHighScore(PlayerScore newScore) { ServletContext ctx = getServletConfig().getServletContext(); AtomicReference holder = (AtomicReference) ctx.getAttribute("highScore"); while (true) { HighScore old = holder.get(); if (old.score >= newScore.score) break; else if (holder.compareAndSet(old, newScore)) break; } } 

Quelle: http://www.ibm.com/developerworks/library/j-jtp09238/index.html