Wie schreibe ich eine einfache Thread-sichere Klasse mit einer flüchtigen Variablen?

8

Ich möchte eine einfache threadsichere Klasse schreiben, die zum Setzen oder Abrufen eines Integer-Wertes verwendet werden könnte.

Am einfachsten ist es, das synchronisierte Schlüsselwort zu verwenden:

%Vor%

Ich könnte auch versuchen, flüchtig :

zu verwenden %Vor%

Ist die Klasse mit dem flüchtigen Schlüsselwort thread-safe ?

?

Betrachten Sie die folgende Abfolge von Ereignissen:

  1. Thread A setzt den Wert auf 5.
  2. Thread B setzt den Wert auf 7.
  3. Thread C liest den Wert.

Aus der Java Language Specification folgt

  • "1" passiert-vor "3"
  • "2" passiert-vor "3"

aber ich sehe nicht, wie es aus der Spezifikation folgen könnte, dass "1" passiert - vor "2", also vermute ich, dass "1" nicht passiert vor "2".

Ich vermute, dass der Thread C 7 oder 5 lesen könnte. Ich denke, die Klasse mit dem volatile Schlüsselwort ist nicht threadsicher und die folgende Sequenz ist ebenfalls möglich:

  1. Thread A setzt den Wert auf 5.
  2. Thread B setzt den Wert auf 7.
  3. Thread C lautet 7.
  4. Thread D liest 5.
  5. Thread C lautet 7.
  6. Thread D liest 5.
  7. ...

Stimmt die Annahme, dass MyIntegerHolder mit volatile nicht threadsicher ist ?

?

Ist es möglich, mit AtomicInteger einen thread-sicheren Integer-Halter zu erstellen:

%Vor%

?

Hier ist ein Fragment des Buches Java Concurrency In Practice:

  

" Lesen und Schreiben von atomaren Variablen haben dieselbe Speichersemantik   als flüchtige Variablen. "

Was ist die beste (vorzugsweise nicht blockierende) Art, einen thread-sicheren MyIntegerHolder zu schreiben?

Wenn Sie die Antwort wissen, würde ich gerne wissen, warum Sie es für richtig halten. Folgt es aus der Spezifikation? Wenn ja, wie?

    
Mateusz Zakrzewski 03.05.2013, 12:09
quelle

5 Antworten

-1

Die Frage war nicht einfach für mich, weil ich (fälschlicherweise) dachte, dass das Wissen über die happes-before -Beziehung ein vollständiges Verständnis des Java-Speichermodells und der Semantik von flüchtig .

Ich fand die beste Erklärung in diesem Dokument: "JSR-133: JavaTM-Speichermodell und Thread-Spezifikation"

Das relevanteste Fragment des obigen Dokuments ist der Abschnitt "7.3 Well-Formed Executions".

Das Java-Speichermodell garantiert, dass alle Ausführungen eines Programms wohlgeformt sind. Eine Ausführung ist nur dann wohlgeformt , wenn sie

ist
  • Obeys passiert - vor der Konsistenz
  • Obeys Konsistenz der Synchronisationsreihenfolge
  • ... (einige andere Bedingungen müssen auch wahr sein)

Happens-vor der Konsistenz ist normalerweise genug, um zu einer Schlussfolgerung über das Programmverhalten zu kommen - aber nicht in diesem Fall, weil ein flüchtiger Schreibvorgang nicht vor passiert ein weiterer flüchtiger schreiben.

Der MyIntegerHolder mit volatile ist threadsicher , aber seine Sicherheit kommt von der Synchronisationsreihenfolgenkonsistenz .

Meiner Meinung nach, wenn Thread B den Wert auf 7 setzen will, informiert A B nicht über alles, was es bis zu diesem Moment getan hat (wie eine der anderen Antworten vorgeschlagen hat) - es informiert nur B über den Wert von die volatile Variable. Thread A würde B über alles (Zuweisung von Werten an andere Variablen) informieren, wenn die von Thread B durchgeführte Aktion gelesen und nicht geschrieben wurde (in diesem Fall würde das passiert-vor Beziehung zwischen den Aktionen, die von diesen beiden Threads ausgeführt werden.

    
Mateusz Zakrzewski 03.05.2013, 19:15
quelle
4

Das Schlüsselwort synchronized besagt, dass wenn Thread A and Thread B auf Integer zugreifen möchte, sie dies nicht gleichzeitig tun können. A sagt B warten, bis ich damit fertig bin.

Auf der anderen Seite macht volatile die Threads "freundlicher". Sie beginnen miteinander zu reden und arbeiten zusammen, um Aufgaben auszuführen. Wenn B versucht, zuzugreifen, informiert A B über alles, was es bis zu diesem Moment getan hat. B ist sich nun der Änderungen bewusst und kann seine Arbeit von dort aus fortsetzen.

In Java haben Sie aus diesem Grund Atomic , die unter dem Deckblatt das volatile Schlüsselwort verwenden, also machen sie ziemlich genau dasselbe, aber sie sparen Ihnen Zeit und Mühe.

Nach was du suchst ist AtomicInteger , du hast Recht damit. Für die Operation, die Sie ausführen möchten, ist dies die beste Wahl.

%Vor%

Um Ihre Frage zu einem allgemeinen Hinweis zu beantworten

Es hängt davon ab, was Sie brauchen. Ich sage nicht synchronized ist falsch und volatile ist gut, sonst hätten die netten Java Leute synchronized vor langer Zeit entfernt. Es gibt keine absolute Antwort, es gibt viele spezifische Fälle und Nutzungsszenarien.

Einige meiner Lesezeichen:

Concurrency-Tipps

Core Java Concurrency

Java-Parallelität

Aktualisieren

Aus der Java Concurrency-Spezifikation verfügbar hier :

  

Paket java.util.concurrent.atomic

     

Ein kleines Toolkit mit Klassen, die Lock-Free Thread-Safe unterstützen   Programmierung auf einzelne Variablen.

%Vor%

Auch von Hier

Die Java-Programmiersprache volatile keyword:

(In allen Versionen von Java) Es gibt eine globale Reihenfolge für die Lese- und Schreibvorgänge in einer flüchtigen Variablen. Dies bedeutet, dass jeder Thread, der auf ein flüchtiges Feld zugreift, seinen aktuellen Wert liest, bevor er fortfährt , anstatt (möglicherweise) einen zwischengespeicherten Wert zu verwenden. (Es gibt jedoch keine Garantie für die relative Reihenfolge von flüchtigen Lese- und Schreibvorgängen mit regulären Lese- und Schreibvorgängen, was bedeutet, dass es im Allgemeinen kein nützliches Threading-Konstrukt ist.)

    
flavian 03.05.2013 12:15
quelle
1

Wenn Sie nur auf eine Variable zugreifen / setzen müssen, ist es ausreichend, um sie wie gewohnt zu deklarieren. Wenn Sie überprüfen, wie AtomicInteger Arbeit setzt / erhält, sehen Sie dieselbe Implementierung

%Vor%

aber Sie können ein flüchtiges Feld nicht atomar so einfach erhöhen. Hier verwenden wir die Methoden AtomicInteger.incrementAndGet oder getAndIncrement.

    
Evgeniy Dorofeev 03.05.2013 12:14
quelle
0
  

Kapitel 17 der Java-Sprachspezifikation definiert die "happen-before" -Beziehung für Speicheroperationen wie das Lesen und Schreiben von gemeinsam genutzten Variablen. Die Ergebnisse eines Schreibens durch einen Thread sind garantiert nur dann für ein Lesen durch einen anderen Thread sichtbar, wenn die Schreiboperation vor der Leseoperation stattfindet.

     
  1. Die synchronisierten und volatilen Konstrukte sowie die Methoden Thread.start () und Thread.join () können hases-before bilden   Beziehungen. Insbesondere: Jede Aktion in einem Thread passiert-vorher   jede Aktion in diesem Thread, die später in der Reihenfolge des Programms kommt.
  2.   
  3. Ein Entsperren (synchronisierter Block oder Methoden-Exit) eines Monitors erfolgt vor jeder nachfolgenden Sperre (synchronisierter Block oder Methode)   Eintrag) desselben Monitors. Und weil die Vor-Vor-Beziehung   ist transitiv, alle Aktionen eines Threads vor dem Entsperren   happen - vor allen Aktionen, die auf das Sperren von Threads folgen   überwachen.
  4.   
  5. Es erfolgt ein Schreibvorgang in ein flüchtiges Feld - vor jedem weiteren Lesen desselben Feldes. Schreibt und liest flüchtige Felder ähnlich   Memory Consistency-Effekte als Eingabe und Beenden von Monitoren, aber tun   keine gegenseitige Ausschlussverriegelung.
  6.   
  7. Ein Aufruf zum Starten eines Threads erfolgt vor jeder Aktion im gestarteten Thread.
  8.   
  9. Alle Aktionen in einem Thread werden ausgeführt, bevor ein anderer Thread erfolgreich von einem Join für diesen Thread zurückgegeben wird.
  10.   

Referenz: Ссылка

aus meinem Verständnis 3 bedeutet: wenn Sie schreiben (nicht basierend Leseergebnis) / lesen ist in Ordnung. Wenn Sie schreiben (basierend auf Leseergebnis, z. B. Inkrementieren) / lesen ist nicht in Ordnung. Da volatile "keine gegenseitige Ausgrenzung Verriegelung"

    
Helin Wang 01.04.2015 00:54
quelle
0

Ihr MyIntegerHolder mit volatile ist threadsicher. Aber AtomicInteger wird bevorzugt, wenn Sie gleichzeitig Programm ausführen, da es auch viele atomare Operationen bietet.

  

Betrachten Sie die folgende Abfolge von Ereignissen:

     
  1. Thread A setzt den Wert auf 5.
  2.   
  3. Thread B setzt den Wert auf 7.
  4.   
  5. Thread C liest den Wert.
  6.   

Aus der Java Language Specification folgt

     
  • "1" passiert vor "3"
  •   
  • "2" passiert vor "3"
  •   

aber ich sehe nicht, wie es aus der Spezifikation, dass "1" folgen könnte   passiert-vor "2" so vermute ich, dass "1"   passiert nicht vor "2".

     

Ich vermute, dass der Thread C 7 oder 5 lesen kann. Ich denke die Klasse mit der   flüchtiges Schlüsselwort ist nicht Thread-sicher

Sie sind hier richtig, dass "1" passiert - bevor "3" und "2" passiert - vor "3". "1" passiert nicht vor "2", aber es bedeutet nicht, dass es nicht Thread-sicher ist. Die Sache ist, dass das von Ihnen angegebene Beispiel nicht eindeutig ist. Wenn Sie sagen "setzt den Wert auf 5", "setzt den Wert auf 7", "liest den Wert" sequenziell, können Sie immer den Wert von 7 lesen. Und es ist Unsinn, sie in verschiedene Threads zu setzen. Aber wenn Sie sagen, dass 3 Threads gleichzeitig ohne Sequenz ausgeführt werden, können Sie sogar den Wert 0 erhalten, weil "liest den Wert" zuerst passieren könnte. Aber das ist nichts mit Thread-safe, es gibt keine Reihenfolge, die von den 3 Aktionen erwartet.

    
Dios-Malone 20.11.2016 19:07
quelle