Nicht thread-safe-Objekt-Publishing
-
06-07-2019 - |
Frage
Beim Lesen "Java Concurrency in der Praxis", da ist dieser Teil in Abschnitt 3.5:
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
Neben dem offensichtlichen Faden Sicherheitsrisiko von zwei Instanzen von Holder
erstellen, das Buch behauptet, ein mögliches Publishing Problem auftreten kann.
Darüber hinaus für eine Holder
Klasse wie
public Holder {
int n;
public Holder(int n) { this.n = n };
public void assertSanity() {
if(n != n)
throw new AssertionError("This statement is false.");
}
}
kann ein AssertionError
geworfen werden!
Wie ist das möglich? Der einzige Weg, ich daran denken kann, kann eine solche lächerliche Verhalten erlauben, wenn der Holder
Konstruktor nicht blockiert werden würde, so wäre ein Verweis auf die Instanz erstellt werden, während der Konstruktor Code läuft immer noch in einem anderen Thread.
Ist das möglich?
Lösung
Der Grund, warum dies möglich ist, ist, dass Java ein schwaches Speichermodell hat. Dabei spielt es keine Bestellung von Lese garantieren und schreibt.
Dieses besondere Problem kann mit den folgenden beiden Code-Snippets, die zwei Threads wiedergegeben werden.
Gewinde 1:
someStaticVariable = new Holder(42);
Thread 2:
someStaticVariable.assertSanity(); // can throw
Auf der Oberfläche scheint es unmöglich, dass dies jemals auftreten könnte. Um zu verstehen, warum dies geschehen kann, müssen Sie über die Java-Syntax zu erhalten und zu einem viel niedrigeren Niveau herunterkommen. Wenn Sie den Code für Thread aussehen 1, es im wesentlichen in eine Reihe von Speichern werden kann abgebaut schreibt und Zuordnungen:
- Alloc Speicher Pointer1
- 42 Schreiben an Pointer1 Offset 0
- Schreiben Zeiger1 zu someStaticVariable
Da Java ein schwaches Speichermodell hat, ist es durchaus möglich, dass der Code tatsächlich in der folgenden Reihenfolge aus der Sicht von 2 Thread ausführen:
- Alloc Memory Pointer1
- Schreiben Zeiger1 zu someStaticVariable
- 42 Schreiben an Pointer1 Offset 0
Scary? Ja, aber es kann passieren.
Was dies bedeutet, ist aber, dass Faden 2 jetzt in assertSanity
nennen kann, bevor n
den Wert bekommen hat 42. Es ist möglich, dass der Wert n
bis zweimal während assertSanity
gelesen werden, einmal vor dem Betrieb # 3 abgeschlossen ist und einmal nach und daher sehen zwei verschiedene Werte und eine Ausnahme aus.
Bearbeiten
Laut Jon Skeet , migh die AssertionError
noch auftreten mit Java 8 es sei denn, das Feld endgültig.
Andere Tipps
Das Java-Speichermodell verwendet sein, so dass die Zuordnung zur Holder
Referenz könnte vor der Zuweisung an die Variable innerhalb des Objekts sichtbar werden.
Allerdings ist das neuere Speichermodell mit Wirkung ab Java 5 macht dies unmöglich, zumindest für die endgültigen Felder: alle Aufgaben innerhalb eines Konstruktor „geschehen, bevor“ jede Zuordnung des Verweises auf das neue Objekt eine Variablen. Sehen Sie sich die Java Language Specification Abschnitt 17.4 um weitere Informationen, aber hier ist das relevanteste snippet:
Eine Aufgabe wird darin gesehen, vollständig initialisiert, wenn seine Konstruktor beendet. Ein Thread, kann nur einen Verweis auf ein Objekt sehen nachdem das Objekt wurde komplett initialisiert wird garantiert das sehen korrekt initialisiert Werte für die Objekt letzte Felder
So Ihr Beispiel könnte immer noch nicht als n
ist nicht endgültig, aber es sollte in Ordnung sein, wenn Sie n
endgültig machen.
Natürlich ist das:
if (n != n)
könnte sicherlich nicht für nicht-final-Variablen, wird die JIT-Compiler vorausgesetzt, es ist nicht optimiert weg - wenn die Operationen sind:
- Fetch LHS: n
- Fetch RHS: n
- Vergleichen LHS und RHS
dann könnte der Wert zwischen den beiden Fetches ändern.
Nun, in dem Buch heißt es, für den ersten Codeblock, der:
Das Problem hier ist nicht der Halter Klasse selbst, sondern dass der Rechteinhaber nicht ordnungsgemäß veröffentlicht. Jedoch, Halter kann immun gemacht werden unsachgemäße Veröffentlichung durch die n Feld deklarieren endgültig zu sein, die Halter machen würde unveränderlich; siehe Abschnitt 3.5.2
Und zum zweiten Codeblock:
Da die Synchronisation nicht verwendet wurde, der Halter für andere sichtbar zu machen Fäden, sagen wir, der Inhaber war nicht ordnungsgemäß veröffentlicht. Zwei Dinge können gehen falsch mit nicht ordnungsgemäß veröffentlicht Objekte. Andere Gewinde könnte sehen abgestanden Wert für den Halter Feld und siehe somit eine Nullreferenz oder andere älterer Wert, obwohl ein Wert wurde in der Halterung platziert. Aber viel schlimmer, andere Threads könnte eine Aufwärts todate sehen Wert für den Halter Referenz, aber abgestanden Werte für den Zustand der Halter. [16] Um die Sache noch weniger vorhersagbar, kann ein Thread einen schalen siehe schätzen sie das erste Mal ein Feld liest und dann ein mehr up-to-date-Wert des das nächste Mal, weshalb assertSanity werfen AssertionError.
Ich denke, JaredPar hat so ziemlich dies explizit gemacht in seinem Kommentar.
(Hinweis:. Nicht für Stimmen suchen hier - Antworten für detaillierte Informationen erlauben als Kommentare)
Das Grundproblem ist, dass ohne richtige Synchronisierung, wie in dem Speicher schreibt in verschiedenen Threads manifestieren kann. Das klassische Beispiel:
a = 1;
b = 2;
Wenn Sie das tun, auf einem Faden, ein zweites Gewinde b Satz 2 sehen kann, bevor ein Weiterhin auf 1 gesetzt ist, dann ist es möglich, dass es eine unbegrenzte Menge an Zeit, die zwischen einem zweiten Thread sein zu sehen, eine dieser Variablen erhalten aktualisiert und die andere Variable aktualisiert wird.
Mit Blick auf diesem von einer gesunden Perspektive, wenn Sie davon ausgehen, dass die Anweisung
if(n != n)
Atom ist (was ich denke, ist vernünftig, aber ich nicht sicher weiß), dann könnte die Behauptung Ausnahme nie geworfen werden.
In diesem Beispiel kommt unter „Ein Verweis auf das Objekt das letzte Feld enthält, entging nicht den Konstruktor“
Wenn Sie instanziiert ein neues Halter-Objekt mit dem neuen Betreiber,
- die Java Virtual Machine werden zuerst vergeben (mindestens) genügend Platz auf dem Heap all Instanzvariablen in Haltern und dessen Super erklärt zu halten.
- Zweitens wird die virtuelle Maschine initialisieren alle Instanzvariablen auf ihre Standardanfangswerte. 3.c Drittens wird die virtuelle Maschine die Methode in der Halter-Klasse aufrufen.
siehe oben für: http://www.artima.com/designtechniques/initializationP.html
Angenommen: 1. Thema beginnt 10.00 Uhr, ruft instatied den Halter Objekt durch den Aufruf des neuen Holer machen (42), 1) die virtuelle Java-Maschine zuerst wird vergeben (mindestens) genügend Platz auf dem Heap all Instanzvariablen in Haltern und dessen Super erklärt zu halten. - wird es Zeit 10.01 2) Zweitens wird die virtuelle Maschine initialisieren alle Instanzvariablen auf ihre Standardanfangswerte - es 10.02 Zeit beginnt 3) Drittens wird die virtuelle Maschine die Methode in der Halter-Klasse aufrufen .-- es wird 10.04 Zeit beginnen
Nun begann Thread2 an -> 10.02.01 Zeit, und es wird einen Anruf assertSanity () 10.03, bis zu diesem Zeitpunkt n mit Standard von Zero, zweitem Thread Lesen der veralteten Daten initialisiert werden.
// unsichere Veröffentlichung öffentlicher Inhaber;
Wenn Sie die öffentlichen Abschluss Inhaber machen dieses Problem beheben
oder
private int n; wenn Sie die private final int n machen; wird dieses Problem Resol.
siehe auch: http: // www. cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html unter Abschnitt Wie letzte Felder unter dem neuen JMM arbeiten?
Ich war auch sehr verwirrt von diesem Beispiel. Ich fand eine Webseite, die das Thema gründlich und Leser erklärt vielleicht nützlich finden: https: // www.securecoding.cert.org/confluence/display/java/TSM03-J.+Do+not+publish+partially+initialized+objects
Edit: Der entsprechende Text aus dem Link sagt:
die JMM ermöglichen Compiler Speicher für den neuen Helper zuzuzuteilen Objekt und einen Verweis auf diesen Speicher zu dem Helferfeld zuweisen bevor das neue Helper Objekt initialisiert wird. Mit anderen Worten, die Compiler kann das Schreiben in die Helfer-Instanz neu anordnen und die schreiben, initialisiert die Helper Object (das heißt, n = this.n), so dass der erstere Fall zuerst eintritt. Dies kann ein Rennen Fenster belichten, während der andere Threads können eine teilweise initialisiert Helper Object beobachten Beispiel.