Speicher Barrieren und coding-Stil über eine Java-VM

Angenommen ich habe eine statische komplexe Objekt wird in regelmäßigen Abständen aktualisiert, die durch einen pool von threads, und Lesen Sie mehr oder weniger ständig in eine lang andauernde-thread. Das Objekt selbst ist immer unveränderlich und spiegelt den neuesten Stand von etwas.

class Foo() { int a, b; }
static Foo theFoo;
void updateFoo(int newA, int newB) {
  f = new Foo();
  f.a = newA;
  f.b = newB;
  //HERE
  theFoo = f;
}
void readFoo() {
  Foo f = theFoo;
  //use f...
}

Kümmert mich nicht im geringsten, ob mein reader sieht das alte oder das neue Foo, aber ich brauche, um zu sehen, ein vollständig initialisiertes Objekt. IIUC, Die Java-Spezifikation besagt, dass ohne eine memory barrier HIER, ich kann sehen, ein Objekt mit f.b initialisiert, aber f.eine noch nicht im Gedächtnis. Mein Programm ist ein real-world-Programm, das wird früher oder später Begehen Sachen zu Speicher, damit ich nicht brauchen, um tatsächlich zu Begehen, der neue Wert von theFoo Speicher sofort (obwohl es würde nicht Schaden).

Was denken Sie, ist die lesbare Art und Weise zu implementieren, die memory-Barriere ? Ich bin bereit zu zahlen ein wenig Leistung Preis der Lesbarkeit zuliebe, wenn es sein muss. Ich denke, ich kann nur synchronisieren Sie die Zuordnung auf " Foo " und " das funktionieren würde, aber ich bin nicht sicher, es ist sehr offensichtlich, jemand liest den code, warum ich das tun. Ich konnte auch synchronisieren Sie die gesamte Initialisierung des neuen Foo, aber das würde die Einführung von mehr sperren, die tatsächlich benötigt werden.

Wie würden Sie es schreiben so, dass es so lesbar wie möglich ?

Bonus-kudos für ein Scala-version 🙂

  • Aus dem Gedächtnis, ich glaube nicht, dass Sie Lesen die Java-spec richtig. Könnten Sie verweisen auf den Abschnitt, wo Sie denken, es sagt, dass.
  • java.sun.com/docs/books/jls/second_edition/html/memory.doc.html und speziell "17.11: Out of order schreibt" verdeutlicht dies (die Regeln selbst sind auf der gleichen Seite am 17.2 und 17.3). Ich kann Lesen, falsch aber.
  • Bär: ich glaube, @Jean ist richtig in seiner Einschätzung hier... siehe meine Antwort und schieben Sie Sie zurück, wenn ich habe es falsch verstanden. Lesen Sie die JMM Besonderheiten jeden Fall erinnert eine Wurst gemacht!
  • Ich Stimme mit Ihr (und Jean) Bewertung und abgestimmt haben, für Ihre Antwort.
  • gute Bearbeitungen zu Ihrer Frage. Ich habe versucht zu Adresse Lesbarkeit, aber wie alles andere Parallelität Verwandte, die Dokumentation im code ist der beste Weg, um es lesbar. Sie können auch die JCiP Parallelität Anmerkungen zu Programmatischer Sachen Dokument (D. H., @Immutable, @ProtectedBy, etc...): javaconcurrencyinpractice.com/annotations/doc/index.html
  • Ich denke, so. final und volatile klingen wie gute und klare technische Lösungen; der rest der Lesbarkeit wird kommen aus eine gute Dokumentation. Vielen Dank für den Hinweis auf JCiP Parallelität Anmerkungen: Sie wird in der Tat die Lesbarkeit zu verbessern.

InformationsquelleAutor Jean | 2010-10-18
  1. Ich mag diese Antwort: "volatile können verwendet werden, um sicher zu veröffentlichen unveränderliche Objekte", die ordentlich kapselt die meisten der Bereich könnte man erwarten, von einem Anwendungsprogrammierer.
  2. @mdma ist Antwort hier: "volatile ist sehr nützlich in der lock-freie algorithmen", fasst eine andere Klasse von Anwendungen—Besondere Zwecke, lock-freie algorithmen, die ausreichend Leistung sensible verdient eine sorgfältige Analyse und überprüfung durch einen Sachverständigen.
  3. Sichere Veröffentlichung über flüchtige

    Folgenden bis auf @Jed Wesley-Smith, es scheint, dass volatile bietet nun stärkere Garantien (seit JSR-133), und die frühere Behauptung", die Sie verwenden können volatile sofern das Objekt veröffentlicht ist unveränderlich" ist ausreichend, aber vielleicht nicht notwendig.

    Blick auf die JMM FAQ, die beiden Einträge Wie final Bereichen arbeiten unter den neuen JMM? und Was bedeutet volatile machen? nicht wirklich befasst mit zusammen, aber ich denke, dass die zweite gibt uns, was wir brauchen:

    Der Unterschied ist, dass es jetzt kein
    mehr so leicht neu zu ordnen normalen Bereich
    Zugriffe um Sie herum. Schreiben
    volatile-Feld hat den gleichen Speicher
    Wirkung wie bei einem monitor release, und
    Lesen aus einer flüchtigen Feld hat die
    gleiche memory-Effekt, wie ein monitor
    erwerben. In Effekt, da das neue
    memory-Modell stellen strengere
    Einschränkungen für die Neuordnung des flüchtigen
    Feld-Zugriffe mit anderen Feld
    Zugriffe, flüchtige oder nicht, nichts
    das war sichtbar zu thread A, wenn es
    schreibt flüchtigen Feld f wird
    sichtbar, thread B, wenn es liest f.

    Ich werde beachten Sie, dass trotz mehrerer rereadings von JCiP, den entsprechenden text dort nicht springen heraus auf mich, bis Jed hingewiesen. Es ist auf p. 38, Abschnitt 3.1.4, und es sagt mehr oder weniger das gleiche wie das vorhergehende Zitat-das veröffentlichte Objekt müssen nur effektiv unveränderlich, keine final erforderlichen Felder ein, QED.

    Älteren Sachen, gehalten für die Rechenschaftslegung

    Einen Kommentar: irgendeinen Grund, warum newA und newB können nicht als Argumente an den Konstruktor? Dann verlassen Sie sich auf die Veröffentlichung von Regeln für Konstruktoren...

    Auch, mit einem AtomicReference wahrscheinlich klärt Unsicherheiten (und vielleicht kaufen Sie auch andere Vorteile, je nachdem, was Sie brauchen, zu tun bekommen in den der rest der Klasse...) Auch jemand schlauer als ich kann Ihnen sagen, wenn volatile würde dieses Problem lösen, aber es scheint immer kryptisch für mich...

    In der weiteren rezension, ich glaube, dass der Kommentar von @Burleigh Bär oben ist korrekt --- (EDIT: siehe unten), , die Sie eigentlich nicht haben, um sorgen über die out-of-sequence-Bestellung hier, da veröffentlichen Sie ein neues Objekt theFoo. Während ein anderer thread wäre denkbar, siehe inkonsistente Werte für newA und newB wie beschrieben in JLS 17.11, dass kann hier nicht passieren, weil Sie begangen werden, zu dem Speicher, bevor der andere thread bekommt ahold einen Verweis auf die neue f = new Foo() Instanz, die Sie erstellt haben... das ist sicher eine Zeit-Veröffentlichung. Auf der anderen Seite, wenn Sie schrieb

    void updateFoo(int newA, int newB) {
      f = new Foo(); theFoo = f;     
      f.a = newA; f.b = newB;
    }


    In diesem Fall ist aber die Synchronisierung Probleme sind relativ transparent, und die Bestellung ist die am wenigsten Ihrer sorgen. Für einige nützliche Anleitungen auf volatil, nehmen Sie einen Blick auf diese developerWorks Artikel.

    Jedoch, haben Sie möglicherweise ein Problem bei separaten reader threads sehen kann, wird der alte Wert für theFoo für unbegrenzte Mengen an Zeit. In der Praxis ist dies nur selten geschieht. Aber die JVM erlaubt ist, cache entfernt den Wert des theFoo Referenz in einem anderen thread-Kontext. Ich bin ziemlich sicher, dass die Kennzeichnung theFoo als volatile werde diese Adresse, so wird jede Art von synchronizer oder AtomicReference.

    • Du hast Recht, es kann und es ist eigentlich in einem Konstruktor in meinem eigentlichen code. Mir war nicht bewusst, gab es keine spezifischen Regeln für die Veröffentlichung für Konstruktoren, so Suche ich in diese Richtung. Danke für den Tipp 🙂
    • Also ich kann mich irren über spezielle Behandlung für Konstruktoren. Ich glaube, dass diese Besondere Behandlung nur gilt für final Felder (die ich denke, dir wäre in diesem Fall, wenn Sie haven ' T gemacht, dass explizite). Siehe cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf, um alle Verweise zu Konstruktoren sind im Rahmen der Initialisierung final Felder.
    • Boah. Vielen Dank, für die vielen Recherchen und die ausführliche und leicht verständliche Erklärung. Jetzt weiß ich, wie ich schreiben sollte, mein code. Ich danke Ihnen sehr.
    • Nur der Vollständigkeit halber, werde ich hinzufügen, dass, wenn Sie versuchen, um zu maximieren die Lesbarkeit, ich würde vergessen, alles, und einfach synchronisieren Sie die beiden Methoden.
    • Ich bekomme es nicht. Warum gehst du nicht einfach erklären, theFoo volatil. Sie können dann loszuwerden synchronisiert. Sie sparen auch Zeit, weil eine volatile schreiben, ist etwa 1/3 der Kosten für einen monitor betreten/verlassen. Ich sehe es an Ihre Antwort, aber ich würde behaupten, wäre es besser lesbar.
    • V: Meine Antwort beinhaltet die "nur verwenden, flüchtige" Antwort, aber ich denke, viele würden dem widersprechen, dass es besser lesbar wird. In meiner Erfahrung volatile ist ein viel-missverstanden Ecke von Java, erfordert eine sorgfältige Analyse, um sicherzustellen, dass er richtig ist, und kann leicht verloren gehen, wie die Klasse ist im Laufe der Zeit entwickelt. Ich habe versucht, die Antwort zu geben auf die Frage, aber beachten Sie auch, dass synchronized wird verstanden werden, indem mehr Leute, die dauerhaft ist über einen größeren Bereich verwendet, und ist wahrscheinlich bevorzugt es sei denn, es ist ein gut verstanden-performance-Treiber.
    • V: JCiP rät von Verwendung von flüchtigen, außer, wenn Sie "vereinfachen implementieren[ation] und verif[Erinnerungen]", und weist die Umsetzer in Richtung Atomic-Variablen, die sich "oft verwendet werden, als bessere volatile Variablen." (JCiP 3.1.1), Während ich mich normalerweise nicht auf Autorität berufen, JCiP hat einen guten Ruf, und ich Neige dazu, zu verschieben, es sei denn, ich habe konkrete Beweise für das Gegenteil.
    • Beachten Sie auch, dass volatile funktioniert nur zuverlässig in dem speziellen Fall, dass unveränderliche Objekte. Während ich schob den Fragesteller in dieser Richtung, ich bin mir nicht sicher, wir können davon ausgehen... wenn die theFoo ist wirklich wandelbar ist oder wird also in Zukunft volatile wird keine Garantie für die Bestellung, die Konsistenz oder die Sichtbarkeit der Zukunft schreibt, um seine Felder.
    • Ich bin damit einverstanden, dass es wäre besser, eine AtomicReference. Aber das volatile-Schlüsselwort seit 1.5 sorgt, passiert, bevor Sie bestellen. Es wird garantiert nicht die Anforderung, die Konsistenz und Transparenz genauso wie eine AtomicReference würde. Ich argumentierte, dass in Ihrem Beispiel, halten alle anderen Codes die gleichen, die den Bereich volatile wäre meiner Meinung nach eine bessere Umsetzung.
    • Zur besseren Lesbarkeit können Sie machen, dass argument. Es ist mehr offensichtlich, dass die variable theFoo bewacht wird gegen jedes Objekt, dessen Synchronisation auf. Aber ich denke, wenn Sie nicht über akzeptable Dokumentation, die auf das Feld selbst dann kann es auch sein verloren in der Zukunft. Einer der größten Punkte in JCiP zu dokumentieren, ist thread-Sicherheit.
    • Versehentlich gelöscht, dieser Kommentar - @andersoj Letzten Punkt, den ich verpasst eines der letzten Dinge, die du geschrieben hast. Heute haben die meisten Prozessoren bieten flüchtigen liest nah zu sein, ein normales Lesen. Wenn wir gehen für den reinen Durchsatz, und es gibt eine Menge liest, ist der Unterschied ziemlich erheblich.
    • V: Das ist eine gute information... ich Frage mich, ob es eine Quelle, die quantifiziert, die irgendwie... (aus google scholar...)
    • dein argument, dass flüchtige funktioniert nur korrekt mit der unveränderlichen Objekte ist nicht korrekt (es war unter der alten JMM aber unter den neuesten JMM es nicht - siehe 17.4.7) – alle Schreibvorgänge vor dem volatile-schreiben passieren-bevor die flüchtigen Lesen. Wie ich schon sagte, anderswo Finale ist so ziemlich immer vorzuziehen, jedenfalls aber für seine zusätzliche Garantien.
    • Wesley-Smith vereinbart, ich denke, wir kreuzten, wie ich reagiert wurde... kann Sie Lesen den neuen text ein und sehen Sie, wenn ich habe es jetzt?
    • JMM: java.sun.com/docs/books/jls/third_edition/html/memory.html
    • Y, insbesondere 17.4.4
    • sieht alles gut jetzt große Antwort.

  • Schreibe einen Kommentar