Gleichzeitiges Lesen / Schreiben in eine Variable in Java

8

Wenn ich eine Variable habe, von der mehrere Threads gelesen werden und nur ein Thread schreibt, muss ich eine Sperre um diese Variable haben? Würde es abstürzen, wenn ein Thread versucht zu lesen und der andere Thread gleichzeitig versucht zu schreiben?

    
ntkachov 01.08.2011, 01:45
quelle

3 Antworten

8

Die Nebenläufigkeit betrifft nicht den Absturz, sondern die Version der Daten, die Sie sehen.

  • Wenn die Umgebungsvariable atomar geschrieben wird, ist es möglich, dass ein (Lese-) Thread einen veralteten Wert liest, wenn Sie der Meinung waren, dass Ihr (Writer-) Thread die Variable aktualisiert hat. Sie können flüchtige Schlüsselwörter verwenden, um zu verhindern, dass Leser-Threads in dieser Situation einen veralteten Wert lesen.

  • Wenn die Schreiboperation nicht atomar ist (z. B. wenn es sich um ein zusammengesetztes Objekt irgendeiner Art handelt und Sie Bits davon gleichzeitig schreiben, während die anderen Threads es theoretisch lesen könnten), dann wären Sie besorgt auch, dass einige Leser-Threads die Variable in einem inkonsistenten Zustand sehen könnten. Sie verhindern dies, indem Sie den Zugriff auf die Variable während des Schreibens sperren (langsam) oder sicherstellen, dass Sie atomar schreiben.

Schreibt in einige Typen von Feldern sind atomare, aber ohne eine happes-before -Beziehung, die korrekte Speicherordnung gewährleistet (es sei denn, Sie verwenden volatile ); Einzelheiten hierzu finden Sie auf dieser Seite .

    
Steve B. 01.08.2011, 01:52
quelle
4

Die einfache Antwort ist ja, Sie brauchen Synchronisation.

Wenn Sie jemals in ein Feld schreiben und es von einem beliebigen anderen Ort aus ohne eine Art von Synchronisation lesen, kann Ihr Programm einen inkonsistenten Zustand sehen und es ist wahrscheinlich falsch. Ihr Programm stürzt nicht ab, sondern kann entweder die alten oder neuen oder (im Falle von Longs und Doubles) halb alte und halb neue Daten sehen.

Wenn ich jedoch von "irgendeiner Form der Synchronisation" spreche, dann meine ich etwas genauer, was eine "Vor-Vor" -Beziehung (auch Speicherbarriere genannt) zwischen den Schreib- und Lesestellen schafft. Synchronisations- oder java.util.concurrent.lock-Klassen sind die naheliegendste Art, eine solche Sache zu erstellen, aber alle parallelen Sammlungen bieten normalerweise auch ähnliche Garantien (überprüfen Sie das javadoc, um sicher zu sein). Wenn Sie z. B. eine Warteschlange für die gleichzeitige Ausführung einer Warteschlange erstellen und übernehmen, wird eine Vor-Vor-Beziehung erstellt.

Das Markieren eines Felds als flüchtig verhindert, dass Sie inkonsistente Referenzen (long-tearing) sehen und garantiert, dass alle Threads einen Schreibvorgang "sehen". Aber flüchtige Felder schreiben / lesen können nicht mit anderen Operationen in größeren atomaren Einheiten kombiniert werden. Die Atomic-Klassen behandeln gängige Combo-Ops wie Compare-and-Set oder Read-and-Increment. Synchronisation oder andere java.util.concurrent Synchronizer (CyclicBarrier, etc) oder Locks sollten für größere Bereiche der Exklusivität verwendet werden.

Ausgehend von dem einfachen Ja gibt es Fälle, die eher "Nein, wenn Sie wirklich wissen, was Sie tun". Zwei Beispiele:

1) Der Spezialfall eines Felds, das NUR während der Bauphase geschrieben und geschrieben wird. Ein Beispiel dafür ist, wenn Sie einen vorberechneten Cache füllen (denken Sie an eine Map, bei der Schlüssel bekannte Werte und Werte vorberechnete abgeleitete Werte sind). Wenn Sie das in einem Feld vor der Konstruktion erstellen und das Feld final ist und Sie später nicht mehr schreiben, führt das Ende des Konstruktors "final field freeze" aus und nachfolgende Lesevorgänge müssen NICHT synchronisiert werden.

2) Der Fall des "racy single check" -Musters, das in Effective Java behandelt wird. Das kanonische Beispiel ist in java.lang.String.hashCode (). String hat ein Hash-Feld, das beim ersten Aufruf von hashCode () träge berechnet und im lokalen Feld zwischengespeichert wird, das NICHT synchronisiert ist. Im Grunde können mehrere Threads rennen, um diesen Wert zu berechnen und über andere Threads setzen, aber weil es von einem bekannten Sentinel (0) bewacht wird und immer den identischen Wert berechnet (es ist uns also egal, welcher Thread "gewinnt" oder ob mehrere tun), das ist eigentlich garantiert in Ordnung.

Eine längere Referenz (von mir geschrieben): Ссылка

    
Alex Miller 02.08.2011 00:37
quelle
2

Seien Sie sich bewusst, dass volatile NICHT atomar ist, was bedeutet, dass Double und Long, die 64 Bits verwenden, in einem inkonsistenten Zustand gelesen werden können, wobei 32 Bits der alte Wert und 32 Bits der neue Wert sind. Außerdem machen flüchtige Arrays die Array-Einträge nicht flüchtig. Es wird dringend empfohlen, Klassen aus java.util.concurrent zu verwenden.

    
Joshua Kaplan 01.08.2011 01:59
quelle

Tags und Links