2010-06-23 5 views
10

In einer schönen article with some concurrency tips wurde ein Beispiel für die folgenden Zeilen optimiert:Unterschied zwischen Synchronisation von Feld liest und flüchtige

double getBalance() { 
    Account acct = verify(name, password); 
    synchronized(acct) { return acct.balance; } 
} 

Wenn ich das richtig verstehe, ist der Punkt der Synchronisation, dass der Wert, um sicherzustellen, ist von acct.balance, die von diesem Thread gelesen werden, ist aktuell und alle ausstehenden Schreibvorgänge in die Felder des Objekts in acct.balance werden ebenfalls in den Hauptspeicher geschrieben.

Das Beispiel ließ mich ein wenig nachdenken: Wäre es nicht effizienter, acct.balance (d. H. Das Feldsaldo der Klasse Account) einfach als volatile zu deklarieren? Es sollte effizienter sein, speichern Sie alle synchronize auf Zugriffe auf acct.balance und würde nicht das gesamte Objekt acct sperren. Fehle ich etwas?

+0

Sie sind richtig, aber der Artikel ist wirklich etwas ganz anderes - lock Umfang zu reduzieren. – gustafc

Antwort

13

Sie haben Recht. volatile bietet eine Sichtbarkeitsgarantie. synchronisiert bietet sowohl eine Sichtbarkeitsgarantie als auch eine Serialisierung von geschützten Codeabschnitten. Für SEHR einfache Situationen ist volatile ausreichend, jedoch ist es leicht, Probleme mit volatilen anstelle von Synchronisation zu bekommen.

Wenn Sie waren davon ausgehen, dass Konto eine Möglichkeit der Anpassung seiner Balance hat dann flüchtig ist nicht gut genug

public void add(double amount) 
{ 
    balance = balance + amount; 
} 

Dann haben wir ein Problem, wenn Balance ohne andere Synchronisation flüchtig ist. Wenn zwei Threads versuchen würde, und rufen add() zusammen könnten Sie haben ein „verpasst“ update wo passiert folgendes

Thread1 - Calls add(100) 
Thread2 - Calls add(200) 
Thread1 - Read balance (0) 
Thread2 - Read balance (0) 
Thread1 - Compute new balance (0+100=100) 
Thread2 - Compute new balance (0+200=200) 
Thread1 - Write balance = 100 
Thread2 - Write balance = 200 (WRONG!) 

Offensichtlich ist dies falsch ist, weil beide Threads den aktuellen Wert und aktualisiert selbstständig lesen und dann schrieb sie zurück (lesen, rechnen, schreiben). volatile hilft hier nicht, also müssten Sie synchronisiert werden, um sicherzustellen, dass ein Thread das gesamte Update abgeschlossen hat, bevor der andere Thread gestartet wurde.

Allgemein finde ich, wenn ich Code schreibe, denke ich "kann ich volatile anstelle von synchronisierten verwenden" könnte die Antwort "ja" sein, aber die Zeit/Mühe, es herauszufinden und die Gefahr, es falsch zu machen ist den Nutzen nicht wert (geringe Leistung).

Nebenbei würde eine gut geschriebene Account-Klasse die gesamte Synch-Logik intern behandeln, so dass Anrufer sich nicht darum kümmern müssen.

1

Deklarieren Konto als flüchtiges unterworfen wird, um folgende Probleme und Einschränkungen

1. „Da andere Threads nicht auf lokale Variablen sehen können, erklären lokale Variablen flüchtig ist sinnlos.“ Wenn Sie außerdem versuchen, eine flüchtige Variable in einer Methode zu deklarieren, erhalten Sie in einigen Fällen einen Compilerfehler.

double getBalance() { flüchtig Konto acct = verify (Name, Passwort); Falsche // .. }

  1. Deklarieren Konto als flüchtiges warnt der Compiler sie holt frisch jedes Mal, anstatt sie in Registern zwischenspeichern. Dies verhindert auch bestimmte Optimierungen, die davon ausgehen, dass kein anderer Thread die Werte unerwartet ändert.

  2. Wenn Sie synchronisiert zu koordinieren Änderungen an Variablen aus unterschiedlichen Threads, flüchtig ist Ihnen nicht garantieren, Atom Zugang, weil eine flüchtige Variable Zugriff nie eine Sperre hält, ist es für die Fälle nicht geeignet ist, wo wir wollen Lesen-Aktualisieren-Schreiben als eine atomare Operation. Es sei denn, Sie sind sicher, dass acct = verify (Name, Passwort); ist ein einzelner atomarer Betrieb, können Sie nicht garantieren, ausgenommene Ergebnisse

  3. Wenn Variable Acct eine Objektreferenz ist, dann ist die Wahrscheinlichkeit, dass es Null sein kann. Wenn versucht wird, eine Synchronisierung für ein Nullobjekt durchzuführen, wird eine NullPointerException mithilfe von synchronized ausgelöst. (weil Sie effektiv auf dem Referenz Synchronisierung nicht das eigentliche Objekt) Wo, wie flüchtig nicht

  4. Stattdessen beschwert Sie eine boolean Variable als flüchtig wie hier

    Private volatile boolean someAccountflag erklären könnte;

    öffentliche void getBalance() { Konto acct; while (! SomeAccountflag) { acct = verify (Name, Passwort); } }

Hinweis: Sie können nicht someAccountflag so synchronisiert erklären, wie Sie nicht mit auf einem primitiven synchronisiert synchronisieren können, synchronisiert funktioniert nur mit Objektvariablen, wobei als primitiv oder Objektvariable flüchtig erklärt werden kann

6. Klasse endgültige statische Felder müssen nicht flüchtig sein, JVM kümmert sich um dieses Problem. So muss das someAccountflag nicht einmal als volatile deklariert werden, wenn es sich um ein finales static handelt oder Sie könnten die faule Singleton-Initialisierung verwenden, um Account als Singleton-Objekt zu deklarieren und wie folgt zu deklarieren: private final static AccountSingleton acc_singleton = new AccountSingleton();

+0

Vielen Dank für die Antwort, aber ich schlug tatsächlich vor, acct nicht als volatile, aber acct.balance zu deklarieren - das heißt, das Feld Gleichgewicht der Klasse Konto. Das ändert einige Ihrer Kommentare. Ich habe versucht, die Frage in dieser Hinsicht ein wenig zu klären. –

1

Wenn mehrere Threads ändern und auf die Daten zugreifen, garantiert synchronized Datenkonsistenz zwischen mehreren Threads.

Wenn ein einzelner Thread die Daten ändert und mehrere Threads versuchen, den letzten Wert der Daten zu lesen, verwenden Sie volatile construct.

Aber für obigen Code garantiert volatile nicht Speicherkonsistenz, wenn mehrere Threds das Gleichgewicht ändern. AtomicReference mit Double Typ erfüllt Ihren Zweck.

Verwandte SE Frage:

Difference between volatile and synchronized in Java

Verwandte Themen