2010-10-27 2 views
10

Meine Frage betrifft sichere Veröffentlichung von Feldwerte in Java (wie hier erläutert Java multi-threading & Safe Publication).Sichere Veröffentlichung, wenn Werte in synchronisierten Methoden gelesen werden

Wie ich es verstehe, kann ein Feld sicher gelesen werden (Zugriff von mehreren Threads Bedeutung wird den richtigen Wert sehen), wenn:

  • Lese- und Schreib auf demselben Monitor synchronisiert
  • Feld endgültig
  • Feld ist flüchtig

Wenn mein Verständnis korrekt die folgende Klasse ist, sollte nicht, da die Thread-sicher sein Der Anfangswert wird ohne diese Merkmale geschrieben. Allerdings kann ich kaum glauben, dass ich firstvolatile machen muss, obwohl nur von einer synchronized Methode zugegriffen wird.

public class Foo { 

    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("hello"); 
      needsGreeting = false; 
     } 
    } 
} 

Fehle ich etwas? Ist der obige Code korrekt und wenn ja, warum? Oder ist es in solchen Fällen notwendig, firstvolatile oder verwenden Sie eine final AtomicBoolean oder etwas Ähnliches zusätzlich für den Zugriff von einer synchronized Methode.

(Nur um zu klären, ich weiß, dass, wenn der Anfangswert in einem Verfahren synchronized geschrieben wurde, wäre es Thread-sicher auch ohne volatile Schlüsselwort sein.)

Antwort

3

Streng genommen, kann ich nicht sehen dass es sicher ist anzunehmen, dass needsGreeting auf wahr gesetzt ist, wenn greet aufgerufen wird.

Damit dies zutrifft, müsste vor der Beziehung zwischen dem ersten Schreiben (das beim Erstellen des Objekts auftritt) und dem ersten Lesen (in der greet -Methode) ein Vorgang stattfinden. Chapter 17 Threads and Locks in dem jedoch JLS, sagt folgende über passiert-vor (hb) Einschränkungen:

17.4.5 Happen-vor Bestellung zwei Aktionen können bestellt werden durch eine geschieht-vor-Beziehung. Wenn eine Handlung stattfindet - vor einer anderen, dann ist die erste sichtbar und bestellt vor der zweiten.

Wenn wir zwei Aktionen x und y haben, schreiben wir hb (x, y), um anzuzeigen, dass x vor y passiert.

  • Wenn x und y sind Aktionen aus dem gleichen Faden und x y kommt vor in der Programmreihenfolge, dann hb (x, y).
  • Zwischen dem Ende eines Konstruktors eines Objekts und dem Beginn eines Finalizers (§12.6) befindet sich eine "happen-before" -Kante für dieses Objekt.
  • Wenn eine Aktion x mit einer folgenden Aktion y synchronisiert wird, haben wir auch hb (x, y).
  • Wenn hb (x, y) undhb (y, z), dann hb (x, z).

Darüber hinaus ist die einzige Möglichkeit, ein synchronisierte-mit Bezug auf einzuführen, das heißt, eine Synchronisation um ist etwas, von dem folgenden zu tun:

Synchronisation Aktionen auslösen, die synchronisierte -mit Beziehung zu Aktionen, wie folgt definiert:

  • Eine Entsperraktion auf Monitor m synchronisiert mit alle nachfolgenden Sperraktionen auf m (wobei Folge entsprechend der Synchronisationsreihenfolge definiert ist).
  • Ein Schreibvorgang in eine flüchtige Variable (§8.3.1.4) v synchronisiert mit allen nachfolgenden Lesevorgängen von v durch einen beliebigen Thread (wobei Folgewert entsprechend der Synchronisationsreihenfolge definiert ist).
  • Eine Aktion, die einen Thread startet, wird synchronisiert - mit der ersten Aktion im Thread, die gestartet wird.
  • Das Schreiben des Standardwerts (null, false oder null) auf jede Variable wird synchronisiert - mit der ersten Aktion in jedem Thread. Obwohl es etwas seltsam erscheint, einen Standardwert für eine Variable zu schreiben, bevor das Objekt, das die Variable enthält, zugewiesen wird, wird konzeptionell jedes Objekt am Anfang des Programms mit seinen voreingestellten Initialisierungswerten erstellt.
  • Die letzte Aktion in einem Thread T1 wird synchronisiert - mit jeder Aktion in einem anderen Thread T2, die erkennt, dass T1 beendet wurde. T2 kann dies durch Aufrufen von T1.isAlive() oder T1.join() erreichen.
  • Wenn Thread T1 den Thread T2 unterbricht, wird der Interrupt von T1 synchronisiert - mit jedem Punkt, an dem ein anderer Thread (einschließlich T2) feststellt, dass T2 unterbrochen wurde (durch Auslösen einer InterruptedException oder durch Aufrufen von Thread.interrupted oder Thread.isInterrupted). .

Es sagt nichts, dass „die Konstruktion eines Objekts vor auf dem Objekt keine Aufrufe von Methoden geschieht. Das geschieht-before Beziehung jedoch besagt, dass gibt es einen Fall-before Kante vom Ende eines Konstruktors eines Objekts an den Anfang eines Finalizers (§12.6) für das Objekt, welches kann ein Hinweis darauf sein, dass es nicht eine Vor-Vor-Kante vom Ende eines Konstruktors eines Objekts gibt zum Beginn einer beliebigen Methode!

+0

"[...] was bedeutet, dass es keine zufällige-vor-Kante vom Ende eines Konstruktors eines Objekts bis zum Anfang einer beliebigen Methode gibt!". Diese "Implikation" ist nicht korrekt. – Grodriguez

+0

Oh, sicher, es ist keine formale Implikation. Die Tatsache, dass sie angeben, dass das Ende des Konstruktors vor dem Start der Finalizer-Methode auftritt, soll jedoch "Hinweise darauf" enthalten, dass dies für beliebige Methoden möglicherweise nicht zutrifft. – aioobe

+0

@aioobe nein, das ist nur für Fälle wie den folgenden Code: 'new SomeObject()', d. H. Aufruf des Konstruktors, aber nicht Speichern der Referenz. Dann kann das Objekt sofort Müll gesammelt werden und der Finalizer kann sofort aufgerufen werden. Der Satz, auf den Sie sich beziehen, stellt nur sicher, dass der Konstruktor immer noch fertig ist, bevor der Finalizer ausgeführt wird. –

4

Zwischen dem Ende eines Konstruktors und Methodenaufrufen gibt es keine "happes Before" -Beziehung. Daher kann ein Thread mit dem Konstruieren der Instanz beginnen und die Referenz verfügbar machen, und ein anderer Thread kann diese Referenz abrufen und mit dem Aufruf der Begrüßung beginnen() Methode auf einem teilweise konstruierten Objekt. Die Synchronisation in Greet() behebt dieses Problem nicht wirklich.

Wenn Sie eine Instanz über das gefeierte, doppelt überprüfte Sperrmuster veröffentlichen, wird es einfacher, das zu sehen. Wenn es eine solche Vor-Zufälligkeits-Beziehung gäbe, hätte es sicher sein sollen, selbst wenn DCLP verwendet wird.

public class Foo { 
    private boolean needsGreeting = true; 

    public synchronized void greet() { 
     if (needsGreeting) { 
      System.out.println("Hello."); 
      needsGreeting = false; 
     } 
    } 
} 

class FooUser { 
    private static Foo foo; 

    public static Foo getFoo() { 
     if (foo == null) { 
      synchronized (FooUser.class) { 
       if (foo == null) { 
        foo = new Foo(); 
       } 
      } 
     } 
     return foo; 
    } 
} 

Wenn mehrere Threads FooUser.getFoo() aufrufen.greet() zur gleichen Zeit, könnte ein Thread die Foo-Instanz konstruieren, aber ein anderer Thread könnte eine Foo-Referenz nicht null finden und greet() aufrufen und findsGeeting ist immer noch falsch.

Ein Beispiel dafür wird in Java Concurrency in Practice (3.5) erwähnt.

+0

Das ist also wahr, auch wenn die Zuweisung zu 'foo' erfolgt * nachdem *' new Foo() 'vollständig ausgewertet wurde? Sie haben eine Referenz, die dies bestätigt? – aioobe

+0

Die Zuweisung zu foo erfolgt nach dem neuen Foo() wird * nur * in diesem Thread aufgerufen, in dem es ausgeführt wird. Aus der Perspektive anderer Threads ist dies möglicherweise nicht der Fall oder scheint es nicht zu sein. Die Compiler und Prozessoren dürfen die Ausführung neu anordnen und Änderungen nicht löschen, solange sie in dem Thread konsistent ist, in dem der Code ausgeführt wird. Darum geht es schließlich bei der Sichtbarkeit. Und deshalb ist DCLP gebrochen: Sichtbarkeit. Der Abschnitt Java Memory Model der Sprachspezifikation wäre eine gute Referenz. Korrekte Sichtbarkeit wird nur mit einer richtigen Vor-Vor-Beziehung bereitgestellt. – sjlee

+0

Dies könnte auch nützlich sein: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html – sjlee

Verwandte Themen