Angenommen, ich habe ein Feld, auf das gleichzeitig zugegriffen wird und das viele Male gelesen und selten beschrieben wird.
public Object myRef = new Object();
Angenommen, ein Thread T1 setzt myRef einmal pro Minute auf einen anderen Wert, während N andere Threads myRef milliardenfach kontinuierlich und gleichzeitig lesen. Ich brauche nur, dass myRef irgendwann für alle Threads sichtbar ist.
Eine einfache Lösung wäre die Verwendung einer AtomicReference oder einfach so flüchtig:
public volatile Object myRef = new Object();
Afaik volatile Reads verursachen jedoch Leistungskosten. Ich weiß, dass es winzig ist, das ist eher etwas, das ich mich wundere, als etwas, das ich tatsächlich brauche. Lassen Sie uns also nicht mit der Leistung befasst sein und davon ausgehen, dass dies eine rein theoretische Frage ist.
Die Frage lautet also : Gibt es eine Möglichkeit, flüchtige Lesevorgänge für Referenzen, auf die nur selten geschrieben wird, sicher zu umgehen, indem Sie etwas an der Schreibstelle tun?
Nach einigem Lesen sieht es so aus, als könnten Speicherbarrieren das sein, was ich brauche. Wenn also ein solches Konstrukt existieren würde, wäre mein Problem gelöst:
- Schreiben
- Barriere aufrufen (synchronisieren)
- Alles wird synchronisiert und alle Threads sehen den neuen Wert. (Ohne dauerhafte Kosten an Leseseiten kann es veraltet sein oder einmalige Kosten verursachen, wenn die Caches synchronisiert werden, aber danach wird alles wieder zum regulären Feld zurückgeführt, bis zum nächsten Schreiben).
Gibt es ein solches Konstrukt in Java oder allgemein? An diesem Punkt kann ich nicht anders, als zu denken, wenn so etwas existiert hätte, wäre es bereits von den viel klügeren Leuten, die diese pflegen, in die Atompakete aufgenommen worden. (Überproportional häufiges Lesen und Schreiben war möglicherweise kein Grund zur Sorge?) Vielleicht stimmt etwas in meinem Denken nicht und ein solches Konstrukt ist überhaupt nicht möglich?
Ich habe gesehen, dass einige Codebeispiele 'volatile' für einen ähnlichen Zweck verwenden und den Vertrag vor dem Vertrag ausnutzen. Es gibt ein separates Synchronisierungsfeld, z. B.:
public Object myRef = new Object();
public volatile int sync = 0;
und beim Schreiben von Thread / Site:
myRef = new Object();
sync += 1 //volatile write to emulate barrier
Ich bin nicht sicher, ob dies funktioniert, und einige argumentieren, dass dies nur auf der x86-Architektur funktioniert. Nach dem Lesen verwandter Abschnitte in JMS funktioniert es meiner Meinung nach nur dann garantiert, wenn dieses flüchtige Schreiben mit einem flüchtigen Lesen der Threads gekoppelt ist, die den neuen Wert von myRef sehen müssen. (So wird das flüchtige Lesen nicht los).
Zurück zu meiner ursprünglichen Frage; ist das überhaupt möglich? Ist es in Java möglich? Ist es in einer der neuen APIs in Java 9 VarHandles möglich?
quelle
sync += 1;
und Ihre Reader-Threads densync
Wert lesen , auch dasmyRef
Update angezeigt wird. Da Sie nur die Leser müssen das Update sehen schließlich , können Sie dies zu Ihrem Vorteil nur lesen Sync auf jedem 1000. Iteration des Lesers Thread oder etwas ähnliches verwenden. Aber Sie können auch einen ähnlichen Trick machenvolatile
- zwischenspeichern Sie einfach dasmyRef
Feld in den Readern für 1000 Iterationen und lesen Sie es dann erneut mit volatile ...sync
Feld gemeint haben , nein, Leser würden dassync
Feld nicht bei jeder Iteration berühren , sie würden es opportunistisch tun, wenn sie überprüfen möchten, ob es ein Update gegeben hat. Das heißt, eine einfachere Lösung wäre, diemyRef
für 1000 Runden zwischenzuspeichern und dann erneut zu lesen ...Antworten:
Grundsätzlich möchten Sie also die Semantik eines
volatile
ohne Laufzeitkosten.Ich denke nicht, dass es möglich ist.
Das Problem ist, dass die Laufzeitkosten von
volatile
auf die Anweisungen zurückzuführen sind, die die Speicherbarrieren im Writer- und Reader-Code implementieren. Wenn Sie den Leser "optimieren", indem Sie seine Speicherbarriere beseitigen, können Sie nicht mehr garantieren, dass der Leser den "selten geschriebenen" neuen Wert sieht, wenn er tatsächlich geschrieben wird.FWIW, einige Versionen der
sun.misc.Unsafe
bieten Klasse explizitloadFence
,storeFence
undfullFence
Methoden, aber ich glaube nicht , dass sie verwendet , wird gegenüber der Verwendung eines beliebigen Leistungsvorteil gebenvolatile
.Hypothetisch ...
Sie möchten, dass ein Prozessor in einem Multiprozessorsystem alle anderen Prozessoren erkennen kann:
Leider unterstützen moderne ISAs dies nicht.
In der Praxis steuert jeder Prozessor seinen eigenen Cache.
quelle
Ich bin mir nicht ganz sicher, ob dies korrekt ist, aber ich könnte dies mithilfe einer Warteschlange lösen.
Erstellen Sie eine Klasse, die ein ArrayBlockingQueue-Attribut umschließt. Die Klasse verfügt über eine Aktualisierungsmethode und eine Lesemethode. Die Aktualisierungsmethode stellt den neuen Wert in die Warteschlange und entfernt alle Werte mit Ausnahme des letzten Werts. Die Lesemethode gibt das Ergebnis einer Peek-Operation in der Warteschlange zurück, dh lesen, aber nicht entfernen. Threads, die das Element an der Vorderseite der Warteschlange spähen, tun dies ungehindert. Threads, die die Warteschlange aktualisieren, tun dies sauber.
quelle
ReentrantReadWriteLock
das Szenario verwenden , das für wenige Schreibvorgänge mit vielen Lesevorgängen ausgelegt ist.Sie können
StampedLock
für den gleichen Fall von wenigen Schreibvorgängen viele Lesevorgänge verwenden, aber auch Lesevorgänge können optimistisch versucht werden. Beispiel:Machen Sie Ihren Status unveränderlich und erlauben Sie kontrollierte Änderungen nur, indem Sie das vorhandene Objekt klonen und nur die erforderlichen Eigenschaften über den Konstruktor ändern. Sobald der neue Status erstellt wurde, weisen Sie ihn der Referenz zu, die von den vielen Lesethreads gelesen wird. Auf diese Weise entstehen beim Lesen von Threads keine Kosten .
quelle