2010-04-05 5 views
8

Das Java-Meomry-Modell schreibt vor, dass synchronize Blöcke, die auf demselben Monitor synchronisieren, eine Vorher-Nachher-Reaktion auf die in diesen Blöcken geänderten Variablen erzwingen. Beispiel:Java-Speichermodell: Neuordnung und gleichzeitige Sperren

// in thread A 
synchronized(lock) 
{ 
    x = true; 
} 

// in thread B 
synchronized(lock) 
{ 
    System.out.println(x); 
} 

In diesem Fall ist es, dass Garantierter Gewinde B x==true solange Faden A sehen, dass bereits bestanden synchronized -block. Jetzt bin ich dabei, viel Code neu zu schreiben, um die flexibleren (und angeblich schneller) Sperren in java.util.concurrent zu verwenden, insbesondere die ReentrantReadWriteLock. So sieht das Beispiel wie folgt aus:

EDIT: Das Beispiel war gebrochen, weil ich den Code falsch umgewandelt, wie durch matt b zur Kenntnis genommen. Wie folgt festgesetzt:

// in thread A 
lock.writeLock().lock(); 
{ 
    x = true; 
} 
lock.writeLock().unlock(); 

// in thread B 
lock.readLock().lock(); 
{ 
    System.out.println(x); 
} 
lock.readLock().unlock(); 

Ich habe jedoch keine Hinweise innerhalb der Speichermodellspezifikation gesehen, die solche Sperren auch die nessessary Ordnung bedeuten. Wenn man sich die Implementierung ansieht, scheint sie auf den Zugriff auf flüchtige Variablen innerhalb von AbstractQueuedSynchronizer (zumindest für die Sun-Implementierung) zu vertrauen. Dies ist jedoch nicht Teil einer Spezifikation, und außerdem wird der Zugriff auf nichtflüchtige Variablen nicht wirklich als durch die Speicherbarriere dieser Variablen abgedeckt betrachtet, oder?

So, hier sind meine Fragen:

  • Ist es sicher, die gleiche Reihenfolge wie bei den „alten“ synchronized Blöcke zu übernehmen?
  • Ist das irgendwo dokumentiert?
  • Zugriff auf eine flüchtige Variable eine Speicherbarriere für jede andere Variable?

Grüße, Steffen

-

Kommentar zu Yanamon:

Blick auf den folgenden Code:

// in thread a 
x = 1; 
synchronized (a) { y = 2; } 
z = 3; 

// in thread b 
System.out.println(x); 
synchronized (a) { System.out.println(y); } 
System.out.println(z); 

Von dem, was ich verstanden, erzwingt die Speicherbarriere die zweite Ausgabe zeigt 2, hat aber keinen Einfluss auf die anderen Variablen ...? Wie kann man das mit dem Zugriff auf eine volatile Variable vergleichen?

+0

Ein Hinweis über den Code, den Sie hinzugefügt haben, Thread b wird nur 2 drucken, wenn es die Sperre für einen vor Thread erhält ... das war irgendwie impliziert, aber ich wollte das nur klarstellen. Aber um Sie zu beantworten volatile Frage, flüchtige in folgender Weise verwendet werden, um Sichtbarkeit zu erzwingen: -------- volatile boolean memoryBarrier = false; int unbewachtWert = 0; // Thread a: unguardedValue = 10; memoryBarrier = wahr; // Thread b if (memoryBarrier) { // unguardedValue wird garantiert als 10 gelesen; } – Yanamon

+0

Nun, ich denke, Code in Kommentaren schreiben funktioniert nicht wirklich gut, ich aktualisierte meine Antwort mit einem Beispiel – Yanamon

Antwort

5

Vom API-doc:

All Lock-Implementierungen dem gleichen Speicher Synchronisation Semantik durch die eingebaute in Monitorverriegelung, wie beschrieben in der Java Language Specification, Third Edition als vorgesehen erzwingen müssen (17.4 Speichermodell):

* A successful lock operation has the same memory synchronization effects as a successful Lock action. 
* A successful unlock operation has the same memory synchronization effects as a successful Unlock action. 

Erfolglos Verriegelungs- und Operationen Entriegeln und reentrant Sperren/entsperren Operationen, nicht erfordern alle Speicher Synchronisation Effekte.

+0

Sie haben absolut Recht. Ich habe eine Menge Dinge gelesen, aber ich habe die Lock-Schnittstelle selbst komplett vermisst ... –

+0

Kannst du die volatile Frage kommentieren? –

+0

@Steffen Heil: Wenn ich mich richtig erinnere, hat jeder Zugriff auf eine flüchtige Variable nur eine Synchronisierwirkung mit anderen Zugriffen auf die gleiche Variable, d. H. Es ist nicht garantiert, irgendeine Art von allgemeiner Speicherbarriere bereitzustellen. Ein kurzer Scan der JLS scheint diese Erinnerung zu bestätigen. Aber nehmen Sie das mit einem riesigen Körnchen Salz, denn es ist schon einige Zeit her, seit ich das Speichermodell und seine Auswirkungen näher kennengelernt habe ... – Dirk

4

Über die Frage hinaus, was die Semantik des Speichermodells garantiert, denke ich, dass es einige Probleme mit dem Code gibt, den Sie posten.

  1. Sie synchronisieren zweimal auf demselben Schloss - dies nicht notwendig ist. Wenn Sie eine Lock Implementierung verwenden, müssen Sie den Block synchronized nicht verwenden.
  2. Das Standard-Idiom für die Verwendung eines Lock ist, dies in einem try-finally-Block zu tun, um ein versehentliches Entsperren des Schlosses zu verhindern (da die Sperre nicht automatisch bei der Eingabe eines Blocks aufgehoben wird, wie beim Block synchronized).

Sie sollten eine Lock mit etwas ähnelnden werden:

lock.lock(); 
try { 
    //do stuff 
} 
finally { 
    lock.unlock(); 
} 
+0

Sie haben Recht, mein Beispiel war gebrochen. Ich habe die Frage geklärt. Und ja, ich benutze in der Regel versuchen/schließlich, nur hier für die Kürze. –

1

Lesen und Schreiben jetzt flüchtigen Variablen erzwingt geschieht vor und geschieht nach der Operation zu sehen. Das Schreiben in eine flüchtige Variable hat die gleiche Wirkung wie das Freigeben eines Monitors und das Lesen einer Variablen hat den Effekt, einen Monitor zu erfassen. Das folgende Beispiel macht es ein wenig mehr klar:

volatile boolean memoryBarrier = false; 
int unguardedValue = 0; 

//thread a: 
unguardedValue = 10; 
memoryBarrier = true; 

// thread b 
if (memoryBarrier) { 
    // unguardedValue is guaranteed to be read as 10; 
} 

Aber das alles gesagt wird der Beispielcode nicht zur Verfügung gestellt sah, wie es wirklich die ReentrantLock Verwendung wurde, wie es verwendet zu werden, wurde entwickelt, um.

  1. Umgebung die Verwendung eines Lock mit der die eingebaute Java in syncronized Stichwort effektiv macht den Zugriff auf das Schloss bereits Single-Threaded, damit es nicht die Lock eine Chance, eine wirkliche Arbeit zu tun, nicht geben.
  2. Der Erwerb einer eine Lock Freigabe sollte unter nach dem Muster durchgeführt werden, das in der Java-Dokumentation von Lock skizzierte

lock.readLock().lock(); 
try { 
    // Do work 
} finally { 
    lock.readLock.unlock(); 
} 

+0

Siehe bitte Kommentar in Frage. –

+0

Ich verstehe, dass Thread b nicht unbewacht sehen wird, da die Reihenfolge die Sichtbarkeit nicht beeinflusst und unguardedValue nicht flüchtig ist, also nicht notwendigerweise von Thread b sichtbar ist. Ist das richtig ? –

1

Yanamon, ich bin nicht sicher, dass Sie richtig sind - aber aus anderen Gründen als das Argument, das Sie machen.

Die Variable unguardedVariable kann im Thread "a" so neu angeordnet werden, dass der Wert auf 10 gesetzt wird, nachdem memoryBarrier auf true festgelegt wurde.

„Es gibt keine Garantie, dass in einer Thread-Operationen werden in der durch das Programm, so lange angegebene Reihenfolge durchgeführt werden, da die Nachbestellung innerhalb dass Fadens nicht nachweisbar ist - selbst wenn die Umordnung ist offensichtlich auf andere Themen "

Java Concurrency in Practice, Brian Goetz, p34

AKTUALISIERT: was ich gesagt ist in dem Fall des alten Speichermodell wahr. Also, wenn du einmal-irgendwo-schreiben willst, dann steht mein Argument. In dem neuen Speichermodell ist dies jedoch nicht der Fall, da die Semantik, die die Neuordnung von nichtflüchtigen Variablen in der Gegenwart eines flüchtigen Zugriffs umgibt, strenger geworden ist (siehe http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile).