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
undvolatile
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.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Kurze Antworten auf die Ursprüngliche Frage
Foo
ist unveränderlich, machen einfach die Felder Finale wird eine komplette Initialisierung und beständige Sichtbarkeit der Felder für alle threads unabhängig der Synchronisation.Foo
ist unveränderlich, Veröffentlichung übervolatile theFoo
oderAtomicReference<Foo> theFoo
ausreichend ist, um sicherzustellen, dass Schreibvorgänge in die Felder sind sichtbar für alle Threads Lesen übertheFoo
ReferenztheFoo
-, reader-threads sind nie garantiert keine update -AtomicReference<Foo>
mit expliziter Synchronisation kommen in der zweiten, und die Verwendung vonvolatile
kommen im DrittenKönnen Sie
volatile
Schuld ich Ihnen. Jetzt ich bin süchtig, ich habe ausgebrochen JCiP, und jetzt Frage ich mich, ob jeder code, den ich jemals geschrieben habe, ist korrekt. Der code-Ausschnitt ist in der Tat potentiell inkonsistent. (Edit: siehe weiter unten den Abschnitt über die Sichere Veröffentlichung über volatilen.)
Den thread Lesen konnte auch sehen abgestanden (in diesem Fall, was auch immer die default-Werte fürkönnen Sie eine der folgenden Optionen, um die Einführung eines passiert-bevor edge:a
undb
waren) für unbegrenzte Zeit.volatile
werden, wodurch eine happens-before-edge-äquivalent zu einemmonitorenter
(Seite Lesen) odermonitorexit
(schreiben-Seite)final
Felder ein und initialisieren Sie die Werte im Konstruktor vor der VeröffentlichungtheFoo
ObjektAtomicInteger
FelderDiese bekommt die schreibreihenfolge gelöst (und löst Ihre Probleme bei der Anzeige). Dann müssen Sie die Sichtbarkeit der Adresse des neuen
theFoo
Referenz. Hiervolatile
geeignet ist -- JCiP sagt in Abschnitt 3.1.4 "Volatile-Variablen" (und hier ist die variable isttheFoo
):Ist, wenn Sie die folgenden, Sie sind golden:
Einfach und Lesbar
Mehrere Leute auf diesem und anderen threads (Dank @Johannes V) beachten Sie, dass die Behörden auf diese Fragen unterstreichen die Bedeutung der Dokumentation von Synchronisation und Annahmen. JCiP Gespräch im detail über dieses, bietet eine Satz von Anmerkungen, die verwendet werden können, für die Dokumentation und statische überprüfung, und Sie können auch einen Blick auf die JMM Kochbuch für Indikatoren zu bestimmten Verhaltensweisen, die erfordern würde, Dokumentation und links zu den entsprechenden Referenzen. Doug Lea hat sich auch bereit eine Liste der Probleme zu berücksichtigen, wenn dokumentieren Parallelität Verhalten. Die Dokumentation ist angemessen, insbesondere wegen der Besorgnis, Skepsis und Verwirrung um concurrency-Probleme (SO: "Java concurrency Zynismus zu weit gegangen?"). Auch tools wie FindBugs sind jetzt eine statische überprüfung der Regeln, zu beachten, Verstöße gegen JCiP annotation der Semantik, wie "Inkonsistente Synchronisierung: IS_FIELD-NOT_GUARDED".
Bis Sie denken, Sie haben einen Grund etwas anderes zu tun, es ist wahrscheinlich am besten zu gehen mit den meisten lesbare Lösung, so etwas wie dies (danke, @Burleigh Bär), mit dem
@Immutable
und@GuardedBy
Anmerkungen.oder, möglicherweise, da es sauberer ist:
Wann ist es angebracht zu verwenden
volatile
Beachten Sie zunächst, dass diese Frage bezieht sich auf die Frage hier, aber angesprochen wurde viele, viele Male auf SO:
In der Tat, eine google-Suche: "site:stackoverflow.com +java +flüchtigen +Schlüsselwort" gibt 355 deutliche Ergebnisse. Verwendung von
volatile
ist am besten, eine flüchtige Entscheidung. Wann ist es sinnvoll? Die JCiP gibt einige abstrakte Orientierung (oben zitiert). Ich werde sammeln einige weitere praktische Richtlinien hier: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.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.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önnenvolatile
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:
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
undnewB
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, wennvolatile
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 ObjekttheFoo
. Während ein anderer thread wäre denkbar, siehe inkonsistente Werte fürnewA
undnewB
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 neuef = new Foo()
Instanz, die Sie erstellt haben... das ist sicher eine Zeit-Veröffentlichung. Auf der anderen Seite, wenn Sie schriebIn 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 destheFoo
Referenz in einem anderen thread-Kontext. Ich bin ziemlich sicher, dass die KennzeichnungtheFoo
alsvolatile
werde diese Adresse, so wird jede Art von synchronizer oderAtomicReference
.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 Initialisierungfinal
Felder.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, dasssynchronized
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.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 dietheFoo
ist wirklich wandelbar ist oder wird also in Zukunftvolatile
wird keine Garantie für die Bestellung, die Konsistenz oder die Sichtbarkeit der Zukunft schreibt, um seine Felder.Haben eine unveränderliche Foo mit der definitiven a-und b-Felder löst die Sichtbarkeit Probleme mit den default-Werten, aber so hat das theFoo volatil.
Persönlich ich mag mit unveränderlichen Wert-Klassen sowieso, da Sie viel schwieriger zu missbrauchen.
AtomicReference
ist ziemlich eindeutig, dass ich Baue, naja, eine Atomare Referenz. 2. Es zwingt client-code die Verwendung von get() und jemand gesund wird-cache den Wert in eine lokale variable. Ich kann mir vorstellen client-code Zugriff auf theFoo.ein und theFoo.b getrennt, und bekommen Werte, die sind beide richtig, aber nicht mit einander übereinstimmen. Ich sehe nicht, theFoo.get().ein und theFoo.get().b so viel.AtomicReference
oder mit der flüchtigen Lösung.