Als wahrscheinlich letzte kleine JSR166-Nachfolge für Mustang haben wir den Atomic-Klassen (AtomicInteger, AtomicReference usw.) eine "lazySet" -Methode hinzugefügt. Dies ist eine Nischenmethode, die manchmal nützlich ist, wenn Code mithilfe nicht blockierender Datenstrukturen optimiert wird. Die Semantik besteht darin, dass der Schreibvorgang garantiert nicht bei einem vorherigen Schreibvorgang neu angeordnet wird, sondern bei nachfolgenden Vorgängen neu angeordnet werden kann (oder für andere Threads entsprechend nicht sichtbar ist), bis eine andere flüchtige Schreib- oder Synchronisierungsaktion auftritt.
Der Hauptanwendungsfall besteht darin, Felder von Knoten in nicht blockierenden Datenstrukturen auf Null zu setzen, um eine langfristige Speicherbereinigung zu vermeiden. Dies gilt, wenn es harmlos ist, wenn andere Threads für eine Weile Nicht-Null-Werte sehen. Sie möchten jedoch sicherstellen, dass Strukturen schließlich GC-fähig sind. In solchen Fällen können Sie eine bessere Leistung erzielen, indem Sie die Kosten für das Null-Volatile-Write vermeiden. Es gibt auch einige andere Anwendungsfälle in diesem Sinne für nicht referenzbasierte Atomics, sodass die Methode in allen AtomicX-Klassen unterstützt wird.
Für Leute, die diese Vorgänge gerne als Barrieren auf Maschinenebene auf gängigen Multiprozessoren betrachten, bietet lazySet eine vorhergehende Store-Store-Barriere (die auf aktuellen Plattformen entweder nicht verfügbar oder sehr billig ist), aber keine Store-Load-Barriere (Dies ist normalerweise der teure Teil eines flüchtigen Schreibvorgangs).
Atomic*
Gültigkeitsbereich haben).lazySet kann für die rmw-Kommunikation zwischen Threads verwendet werden, da xchg atomar ist. Wenn der Writer-Thread-Prozess eine Cache-Zeilenposition ändert, wird dies vom Prozessor des Reader-Threads beim nächsten Lesen angezeigt, da das Cache-Kohärenzprotokoll der Intel-CPU dies garantiert LazySet funktioniert, aber die Cache-Zeile wird beim nächsten Lesen aktualisiert. Auch hier muss die CPU modern genug sein.
http://sc.tamu.edu/systems/eos/nehalem.pdf Für Nehalem, eine Multiprozessor-Plattform, haben die Prozessoren die Möglichkeit, den Adressbus für die Zugriffe anderer Prozessoren auf den Systemspeicher und zu überwachen (zu belauschen) zu ihren internen Caches. Sie verwenden diese Snooping-Fähigkeit, um ihre internen Caches sowohl mit dem Systemspeicher als auch mit den Caches in anderen miteinander verbundenen Prozessoren konsistent zu halten. Wenn ein Prozessor durch Snooping erkennt, dass ein anderer Prozessor beabsichtigt, an einen Speicherort zu schreiben, den er derzeit im freigegebenen Zustand zwischengespeichert hat, macht der Snooping-Prozessor seinen Cache-Block ungültig und zwingt ihn, beim nächsten Zugriff auf denselben Speicherort eine Cache-Zeilenfüllung durchzuführen .
Oracle Hotspot JDK für x86-CPU-Architektur->
lazySet == unsafe.putOrderedLong == xchg rw (asm-Anweisung, die als weiche Barriere dient und 20 Zyklen auf nehelem intel cpu kostet)
Auf x86 (x86_64) ist eine solche Barriere in Bezug auf die Leistung viel billiger als flüchtig oder AtomicLong getAndAdd.
In einem Szenario mit einem Produzenten und einer Konsumentenwarteschlange kann xchg soft barriere die Codezeile vor dem lazySet (Sequenz + 1) erzwingen, damit der Produzententhread ausgeführt wird, BEVOR jeder Konsumententhreadcode die neuen Daten verbraucht (bearbeitet) Der Consumer-Thread muss atomar überprüfen, ob die Producer-Sequenz mithilfe eines compareAndSet (Sequenz, Sequenz + 1) um genau eins erhöht wurde.
Ich habe nach dem Hotspot-Quellcode gesucht, um die genaue Zuordnung des lazySet zum cpp-Code zu finden: http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/unsafe. cpp Unsafe_setOrderedLong -> SET_FIELD_VOLATILE-Definition -> OrderAccess: release_store_fence. Für x86_64 wird OrderAccess: release_store_fence so definiert, dass die Anweisung xchg verwendet wird.
Sie können sehen, wie es in jdk7 genau definiert ist (Doug Lea arbeitet an einigen neuen Dingen für JDK 8): http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/4fc084dac61e/src/os_cpu/ linux_x86 / vm / orderAccess_linux_x86.inline.hpp
Sie können die hdis auch verwenden, um die Assembly des lazySet-Codes in Aktion zu zerlegen.
Es gibt noch eine andere verwandte Frage: Brauchen wir mfence, wenn wir xchg verwenden?
quelle
Eine breitere Diskussion über die Ursprünge und den Nutzen von lazySet und dem zugrunde liegenden putOrdered finden Sie hier: http://psy-lob-saw.blogspot.co.uk/2012/12/atomiclazyset-is-performance-win-for.html
Zusammenfassend: lazySet ist ein schwaches flüchtiges Schreiben in dem Sinne, dass es als Store-Store und nicht als Store-Load-Zaun fungiert. Dies läuft darauf hinaus, dass lazySet JIT zu einem MOV-Befehl kompiliert wird, der vom Compiler nicht neu geordnet werden kann, anstatt zu dem wesentlich teureren Befehl, der für einen flüchtigen Satz verwendet wird.
Wenn Sie den Wert lesen, führen Sie am Ende immer einen flüchtigen Lesevorgang durch (auf jeden Fall mit einem Atomic * .get ()).
lazySet bietet einem einzelnen Writer einen konsistenten flüchtigen Schreibmechanismus, dh es ist absolut legitim, wenn ein einzelner Writer lazySet zum Inkrementieren eines Zählers verwendet. Mehrere Threads, die denselben Zähler inkrementieren, müssen die konkurrierenden Schreibvorgänge mit CAS auflösen. Genau das geschieht unter die Cover von Atomic * für incAndGet.
quelle
StoreStore
Barriere, aber nicht einStoreLoad
?Aus der Zusammenfassung des Concurrent-Atomic-Pakets
lazySet hat die Speichereffekte des Schreibens (Zuweisens) einer flüchtigen Variablen, außer dass es Neuordnungen mit nachfolgenden (aber nicht vorherigen) Speicheraktionen ermöglicht, die selbst keine Neuordnungsbeschränkungen für gewöhnliche nichtflüchtige Schreibvorgänge auferlegen. Unter anderen Verwendungskontexten kann lazySet angewendet werden, wenn aus Gründen der Speicherbereinigung eine Referenz auf Null gesetzt wird, auf die nie wieder zugegriffen wird.
Wenn Sie neugierig auf lazySet sind, schulden Sie sich auch andere Erklärungen
quelle
Hier ist mein Verständnis, korrigieren Sie mich, wenn ich falsch liege: Sie können sich
lazySet()
als "halbflüchtig" vorstellen: Es ist im Grunde eine nichtflüchtige Variable in Bezug auf das Lesen durch andere Threads, dh der von lazySet festgelegte Wert ist für andere möglicherweise nicht sichtbar Fäden. Es wird jedoch flüchtig, wenn eine andere Schreiboperation auftritt (möglicherweise von anderen Threads). Die einzige Auswirkung von lazySet, die ich mir vorstellen kann, istcompareAndSet
. Wenn Sie also verwendenlazySet()
, erhaltenget()
andere Threads möglicherweise immer noch den alten Wert, haben abercompareAndSet()
immer den neuen Wert, da es sich um eine Schreiboperation handelt.quelle
compareAndSet
?Re: Versuch es zu dumm -
Sie können sich dies als eine Möglichkeit vorstellen, ein flüchtiges Feld so zu behandeln, als wäre es für eine bestimmte Speicheroperation (z. B. ref = null;) nicht flüchtig.
Das ist nicht ganz richtig, aber es sollte ausreichen, dass Sie eine Entscheidung zwischen "OK, es ist mir wirklich egal" und "Hmm, lassen Sie mich ein bisschen darüber nachdenken" treffen können.
quelle