2013-05-03 5 views
8

Ich möchte eine einfache Gewindesichere Klasse schreiben, die verwendet werden könnte, um einen Integer-Wert zu setzen oder zu erhalten.Wie schreibe ich eine einfache Thread-sichere Klasse mit einer flüchtigen Variablen?

Der einfachste Weg ist es, das synchronisiert Schlüsselwort zu verwenden:

public class MyIntegerHolder { 

    private Integer value; 

    synchronized public Integer getValue() { 
     return value; 
    } 

    synchronized public void setValue(Integer value) { 
     this.value = value; 
    } 

} 

ich auch versuchen könnte flüchtigen mit:

public class MyIntegerHolder { 

    private volatile Integer value; 

    public Integer getValue() { 
     return value; 
    } 

    public void setValue(Integer value) { 
     this.value = value; 
    } 

} 

Ist die Klasse mit den flüchtigen Stichwort Fäden -safe?

Betrachten Sie die folgende Abfolge von Ereignissen:

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

Aus der Java Language Specification folgt, dass

  • "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" so vermute ich, dass "1" nichtpassieren vor "2".

Ich vermute, dass das Gewinde C 7 oder 5 lesen kann ich glaube, die Klasse mit dem flüchtigen Schlüsselwort ist nicht thread-safe und die folgende Sequenz ist auch möglich:

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

Bin ich richtig, dass MyIntegerHolder mit flüchtigen in der Annahme nicht thread-safe?

Ist es möglich, eine Thread-sichere Integer Halter zu machen, indem Atomicinteger mit:

public class MyIntegerHolder { 

    private AtomicInteger atomicInteger = new AtomicInteger(); 

    public Integer getValue() { 
     return atomicInteger.get(); 
    } 

    public void setValue(Integer value) { 
     atomicInteger.set(value); 
    } 

} 

?

Hier ist ein Fragment des Java Concurrency in der Praxis Buch:

"Liest und schreibt von atomaren Variablen haben die gleichen Speicher Semantik als flüchtige Variablen."

Was ist die beste (vorzugsweise nicht blockierende) Schreibweise Gewindesicher MyIntegerHolder?

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

+0

Ich lerne immer noch, wenn jemand mir etwas klarstellen könnte, wäre es großartig. Wie machen die 'synchronisierten' oder' volatilen' Keywords es _thread sicher_? Da die Integer-Zuweisung und das Lesen von sich aus atomar sind, ändert es irgendetwas? Dieser Ansatz erlaubt keine atomaren Inkremente oder sowieso "get-and-set". Danke – Sebi

+0

@Sebi Thread-Safe ist ein Programm, das in zwei oder mehr Threads ausgeführt wird, ohne dass ein Thread auf einem anderen stört. Sie können dieses Problem lösen, ein Thread, der sich auf andere Weise in andere einmischt. Ein einfaches int x = x + x scheint atomar zu sein, ist aber nicht (mehr dafür flüchtiges und atomares suchen)! Um es "threadsicherer zu machen", ist nur ein Weg, das Problem zu lösen. Das macht MORE nicht thread-sicher, ist nur eine andere Implementierung, um das gleiche Problem zu lösen. –

+0

@ThufirHawat Ich bin mir bewusst, was ein thread-sicheres Programm ist, oder dass die Addition mehrere Lesevorgänge vor dem Schreiben umfasst, aber ich bin mir sicher, dass ich etwas nicht über diese Klasse weiß. Ich kann nicht verstehen, wie eine Klasse, die Getter und Setter für eine ganze Zahl ausstellt, dazu verwendet werden könnte, etwas zu tun, was die Thread-Sicherheit erfordert, ohne extern gesperrt zu sein (wobei "Integer" ausreichen würde). – Sebi

Antwort

-1

Die Frage für mich nicht einfach war, weil ich dachte, (falsch) dass das Wissen alles über die passiert-vor Beziehung gibt ein vollständiges Verständnis des Java Memory Model - und die Semantik von flüchtigen.

fand ich die beste Erklärung in diesem Dokument: "JSR-133: JavaTM Memory Model and Thread Specification"

Das relevanteste Fragment des obigen Dokuments ist der Abschnitt „7.3 Wohlgeformte Exekutionen“.

Das Java-Speichermodell garantiert, dass alle Ausführungen eines Programms wohlgeformt sind. Eine Ausführung ist gut ausgebildet geschützt, wenn es

  • Gehorcht passiert-vor Konsistenz
  • Gehorcht Synchronisation Ordnung Konsistenz
  • ... (einige andere Bedingungen müssen auch wahr sein)

Happen-vor Konsistenz in der Regel genug ist, um eine Aussage über das Programmverhalten zu kommen - aber nicht in diesem Fall, da ein flüchtiges Schreib nicht tut passieren -vor ein anderer flüchtiger schreiben.

Die MyIntegerHolder mit flüchtigen ist thread-safe, aber es ist die Sicherheit von der Synchronisation Ordnung Konsistenz kommt.

Meiner Meinung nach, wenn Thread B den Wert auf 7 setzt, 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 der volatilen Variable. Thread A informiert B über alles (Werte anderen Variablen zuweisen), wenn die Aktion von Thread B gelesen wurde und nicht schreiben (in diesem Fall würde die passiert-vor Beziehung zwischen den Aktionen von diesen beiden Threads).

1

Wenn Sie nur auf eine Variable zugreifen/setzen müssen, ist es genug, um es wie Sie es zu deklarieren. Wenn Sie überprüfen, wie Atomicinteger set/get Arbeit, die Sie die gleiche Implementierung

private volatile int value; 
... 

public final int get() { 
    return value; 
} 

public final void set(int newValue) { 
    value = newValue; 
} 

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

4

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

Auf der anderen Seite macht volatile Fäden "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 Atomic aus diesem Grund, die unter der Abdeckungen das Schlüsselwort volatile verwenden, so dass sie ziemlich genau dasselbe tun, aber sie sparen Ihnen Zeit und Mühe.

Das, was Sie suchen, ist AtomicInteger, Sie haben Recht damit. Für die Operation, die Sie durchführen möchten, ist dies die beste Wahl.

There are two main uses of `AtomicInteger`: 

* As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently 

* As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms. 

Um Ihre Frage zu einem allgemeinen Hinweis

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

Ein paar meiner Lesezeichen:

Concurrency tips

Core Java Concurrency

Java concurrency

aktualisieren

Vom Java Concurrency spezif ication verfügbar here:

Paket java.util.concurrent.atomic

Ein kleines Toolkit von Klassen, die auf einzelne Variablen sperren freien Thread-sichere Programmierung unterstützen.

Instances of classes `AtomicBoolean`, `AtomicInteger`, `AtomicLong`, and `AtomicReference` each provide access and updates to a single variable of the corresponding type. 
Each class also provides appropriate utility methods for that type. 
For example, classes `AtomicLong` and AtomicInteger provide atomic increment methods. 

The memory effects for accesses and updates of atomics generally follow the rules for volatiles: 

get has the memory effects of reading a volatile variable. 
set has the memory effects of writing (assigning) a volatile variable. 

Auch von Here

Die Programmiersprache Java volatile Stichwort:

(In allen Versionen von Java) Es ist eine globale Ordnung auf der liest und schreibt in einem volatilen Variable . Dies bedeutet, dass jeder Thread, der auf ein flüchtiges Feld zugreift seinen aktuellen Wert liest, bevor er fortsetzt, anstatt (möglicherweise) einen zwischengespeicherten Wert zu verwenden. (Es gibt jedoch keine Garantie über die relative Anordnung von flüchtigem liest und schreibt regelmäßig liest und schreibt, was bedeutet, dass es in der Regel nicht ein nützliches Threading-Konstrukt.)

+0

Ich verstehe deine Antwort, aber die Frage ist nicht so einfach. Ich denke, der MyIntegerHolder mit volatile ist nicht thread-sicher und ich legte meine Argumentation vor. Meiner Meinung nach muss sich die richtige Antwort auf die Spezifikation und die Definition der Vor-Vor-Beziehung beziehen. Vielleicht liege ich falsch, und der MyIntegerHolder ist Thread-sicher - aber in diesem Fall sollte die Antwort erklären, was der richtige Weg ist, die Definition der Vor-Vor-Beziehung zu verstehen. Oder vielleicht ist das Problem nicht mit dem Code (vielleicht ist es richtig), aber mit der Spezifikation (vielleicht ist es nicht klar genug). –

+0

"A informiert B über alles, was es bis zu diesem Moment getan hat" - bist du sicher? Folgt es aus der Spezifikation? In der Definition der "happes-before" -Beziehung können Sie sehen, dass das Schreiben passiert - vor dem Lesen, aber ich habe nicht gesehen, dass das Fragment sagt, dass Schreiben passiert - vor einem anderen Schreiben. –

+0

Ich weiß, dass AtomicInteger volatilen Ganzzahl ähnlich ist.Aber die Frage dreht sich wirklich um das richtige Verständnis der in der [Java Language Specification] definierten Beziehung happes-before (http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#) jls-17.4.5) –

0

Kapitel 17 der Java-Sprachspezifikation definiert die "happes-before" -Relation für Speicheroperationen wie Lese- und Schreibvorgänge für gemeinsam genutzte 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 flüchtige Konstrukte sowie die Thread.start() und Thread.join() -Methoden können bilden geschieht-before Beziehungen. Insbesondere: Jede Aktion in einem Thread passiert - vor jede Aktion in diesem Thread, die später in der Reihenfolge des Programms kommt.
  2. Eine Entsperrung (synchronisierter Block oder Methodenausgang) eines Monitors erfolgt vor jeder nachfolgenden Sperre (synchronisierter Block oder Methode Eintrag) desselben Monitors. Und weil die passiert-vorher-Beziehung transitiv ist, passieren alle Aktionen eines Threads vor dem Entsperren - vor allen Aktionen, die auf einen Thread folgen, der diesen Monitor sperrt.
  3. Ein Schreiben in ein flüchtiges Feld geschieht - vor jedem weiteren Lesen desselben Feldes. Schreibt und liest flüchtige Felder haben ähnliche Memory-Konsistenz-Effekte als Eingabe und Beenden von Monitoren, aber keine gegenseitige Ausschluss Sperre.
  4. Ein Aufruf zum Starten eines Threads erfolgt vor einer Aktion im gestarteten Thread.
  5. Alle Aktionen in einem Thread passieren - bevor ein anderer Thread erfolgreich von einem Join in diesem Thread zurückkehrt.

Referenz: http://developer.android.com/reference/java/util/concurrent/package-summary.html

aus meinem Verständnis 3 bedeutet: Wenn Sie (nicht auf Basis Leseergebnis) Schreib-/Lese ist in Ordnung. Wenn Sie schreiben (basierend auf Leseergebnis, z. B. Inkrementieren)/lesen ist nicht in Ordnung. Da die Volatilität "keine gegenseitige Ausschlusssperre beinhaltet"

0

Ihr MyIntegerHolder mit flüchtigen ist Thread sicher. 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 den Wert 5.
  2. Thread B setzt setzt den Wert auf 7.
  3. Gewinde C liest den Wert.

Aus der Java Language Specification folgt, dass

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

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

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

Sie hier sind, dass „1“ passiert-vor „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, da ist keine Reihenfolge von den 3 Aktionen zu erwarten.

Verwandte Themen