Emulieren einer Speicherbarriere in Java, um flüchtige Lesevorgänge zu vermeiden

8

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?

sydnal
quelle
1
Für mich klingt es so, als wären Sie weit in dem Gebiet, in dem Sie einige tatsächliche Benchmarks schreiben und ausführen müssen, die Ihre Arbeitslast simulieren.
NPE
1
Das JMM gibt an, dass dies bei Ihrem Writer-Thread der Fall ist sync += 1; und Ihre Reader-Threads den syncWert lesen , auch das myRefUpdate 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 machen volatile- zwischenspeichern Sie einfach das myRefFeld in den Readern für 1000 Iterationen und lesen Sie es dann erneut mit volatile ...
Petr Janeček
@ PetrJaneček Aber muss er nicht den Zugriff auf die Zählervariable synchronisieren, die von Thread gemeinsam genutzt wird? Wird das nicht ein Engpass sein? Meiner Meinung nach wäre das noch teurer.
Ravindra Ranwala
@ RavindraRanwala Jeder Leser hat seinen eigenen Zähler, wenn Sie bis zu den 1000 Iterationen zählen möchten. Wenn Sie das syncFeld gemeint haben , nein, Leser würden das syncFeld 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, die myReffür 1000 Runden zwischenzuspeichern und dann erneut zu lesen ...
Petr Janeček
@ PetrJaneček danke, ich habe darüber als mögliche Lösung nachgedacht. Aber ich frage mich, ob dies mit einer generischen, soliden Implementierung möglich ist.
Sydney

Antworten:

2

Grundsätzlich möchten Sie also die Semantik eines volatileohne Laufzeitkosten.

Ich denke nicht, dass es möglich ist.

Das Problem ist, dass die Laufzeitkosten von volatileauf 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.Unsafebieten Klasse explizit loadFence, storeFenceund fullFenceMethoden, aber ich glaube nicht , dass sie verwendet , wird gegenüber der Verwendung eines beliebigen Leistungsvorteil geben volatile.


Hypothetisch ...

Sie möchten, dass ein Prozessor in einem Multiprozessorsystem alle anderen Prozessoren erkennen kann:

"Hey! Was auch immer Sie tun, machen Sie Ihren Speichercache für die Adresse XYZ ungültig und tun Sie es jetzt."

Leider unterstützen moderne ISAs dies nicht.

In der Praxis steuert jeder Prozessor seinen eigenen Cache.

Stephen C.
quelle
Ich sehe, dieser hypothetische Teil Ihrer Antwort ist das, wonach ich gesucht habe. Vielen Dank.
Sydney
1

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.

djhallx
quelle
1
  • Sie können ReentrantReadWriteLockdas Szenario verwenden , das für wenige Schreibvorgänge mit vielen Lesevorgängen ausgelegt ist.
  • Sie können StampedLockfür den gleichen Fall von wenigen Schreibvorgängen viele Lesevorgänge verwenden, aber auch Lesevorgänge können optimistisch versucht werden. Beispiel:

    private StampedLock lock = new StampedLock();
    
    public void modify() {            // write method
        long stamp = lock.writeLock();
        try {
          modifyStateHere();
        } finally {
          lock.unlockWrite(stamp);
        }
    } 
    
    public Object read() {            // read method
      long stamp = lock.tryOptimisticRead();
      Object result = doRead();       //try without lock, method should be fast
      if (!lock.validate(stamp)) {    //optimistic read failed
        stamp = lock.readLock();      //acquire read lock and repeat read
        try {
          result = doRead();
        } finally {
          lock.unlockRead(stamp);
        }
      }
      return result;
    }
  • 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 .

diginoise
quelle
Wenn Sie Lust auf Downvoting haben, geben Sie bitte an, warum, damit der Autor und die Community lernen können
Diginoise
In meinem Szenario ist es nicht möglich, es unveränderlich zu machen. Und ich wäre ziemlich überrascht, wenn das gestempelte Schließfach weniger Kosten verursacht als ein einfaches flüchtiges Lesen. Ich werde es jedoch versuchen, danke.
Sydney