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?

War es hilfreich?

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:

  1. Alloc Speicher Pointer1
  2. 42 Schreiben an Pointer1 Offset 0
  3. 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:

  1. Alloc Memory Pointer1
  2. Schreiben Zeiger1 zu someStaticVariable
  3. 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,

  1. 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.
  2. 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top