Führt das Umsetzen eines Objekts in eine ConcurrentHashMap zu einer Speicher-Relation "vorher-vorher"?

7

Ich arbeite mit existierendem Code, der einen Objektspeicher in Form einer ConcurrentHashMap hat. Innerhalb der Map werden veränderbare Objekte gespeichert, die von mehreren Threads verwendet werden. Keine zwei Threads versuchen, ein Objekt auf einmal zu ändern. Mein Anliegen ist die Sichtbarkeit der Änderungen zwischen den Threads.

Gegenwärtig hat der Code des Objekts eine Synchronisation auf die "Setter" (geschützt durch das Objekt selbst). Es gibt keine Synchronisierung auf den "Gettern" noch sind die Mitglieder flüchtig. Dies würde bedeuten, dass die Sichtbarkeit nicht gewährleistet ist. Wenn ein Objekt jedoch geändert wird, wird erneut in die Karte eingefügt (die Methode put() wird erneut aufgerufen, gleicher Schlüssel). Bedeutet dies, dass, wenn ein anderer Thread das Objekt aus der Karte zieht, es die Änderungen sehen wird?

Ich habe dies hier in stackoverflow, in JCIP und in der Paketbeschreibung für java.util.concurrent untersucht. Ich habe mich selbst verwirrt, denke ich ... aber der letzte Strohhalm, der mich veranlasste, diese Frage zu stellen, war aus der Paketbeschreibung, es heißt:

  

Aktionen in einem Thread vor dem Platzieren eines Objekts in einer gleichzeitigen Auflistung geschehen-bevor-Aktionen nach dem Zugriff oder Entfernen dieses Elements aus der Auflistung in einem anderen Thread.

Beziehen sich "Aktionen" in Bezug auf meine Frage auf die Änderungen an den Objekten, die vor dem re-put () in der Karte gespeichert wurden? Wenn das alles zu Threads führt, ist dies ein effizienter Ansatz? Ich bin relativ neu in Threads und würde mich über Ihre Kommentare freuen.

Bearbeiten:

Danke euch allen für eure Antworten! Dies war meine erste Frage zu StackOverflow und es war sehr hilfreich für mich.

Ich muss mit ptomli antworten, weil ich denke, dass es meine Verwirrung am deutlichsten anspricht. Das Festlegen einer "Vor-Vor" -Beziehung beeinflusst in diesem Fall nicht unbedingt die Modifizierungssichtbarkeit. Meine "Titelfrage" ist in Bezug auf meine eigentliche Frage, die im Text beschrieben wird, schlecht aufgebaut. ptomli antwortet jetzt mit dem, was ich in gelesen habe JCIP : "Um sicherzustellen, dass alle Threads die aktuellsten Werte der gemeinsam genutzten veränderbaren Variablen sehen, müssen die Lese- und Schreib-Threads auf einer gemeinsamen Sperre synchronisiert werden" (Seite 37). Das Zurücksetzen des Objekts in die Map stellt diese allgemeine Sperre für die Änderung der Member des eingefügten Objekts nicht bereit.

Ich schätze alle Änderungstipps (unveränderliche Objekte usw.) und stimme ihnen von ganzem Herzen zu. Aber für diesen Fall, wie ich erwähnt habe, gibt es keine gleichzeitige Änderung wegen sorgfältiger Thread-Handhabung. Ein Thread ändert ein Objekt und ein anderer Thread liest später das Objekt (wobei das CHM der Objektförderer ist). Ich denke, die CHM ist nicht ausreichend, um sicherzustellen, dass der später ausführende Thread die Änderungen von der ersten unter Berücksichtigung der von mir bereitgestellten Situation sieht. Ich denke jedoch, dass viele von Ihnen die Titelfrage richtig beantwortet haben.

    
ryanlb1000 18.10.2011, 14:43
quelle

6 Antworten

2

Ich denke, Ihre Frage bezieht sich mehr auf die Objekte, die Sie in der Karte speichern, und darauf, wie sie auf den gleichzeitigen Zugriff reagieren, als auf die gleichzeitige Karte selbst.

Wenn die Instanzen, die Sie in der Map speichern, synchronisierte Mutatoren haben, aber keine synchronisierten Accessoren, dann sehe ich nicht, wie sie Thread-sicher sein können, wie beschrieben.

Nehmen Sie die Map aus der Gleichung und bestimmen Sie, ob die Instanzen, die Sie speichern, selbst Thread-sicher sind.

  

Wenn jedoch ein Objekt geändert wird, wird es wieder in die Map eingefügt (die Methode put () wird erneut aufgerufen, der gleiche Schlüssel). Bedeutet dies, dass, wenn ein anderer Thread das Objekt aus der Karte zieht, es die Änderungen sehen wird?

Dies veranschaulicht die Verwirrung. Die Instanz, die in die Map eingefügt wird, wird von der Map durch einen anderen Thread abgerufen. Dies ist die Garantie für die gleichzeitige Karte. Das hat nichts mit der Sichtbarkeit des Zustands der gespeicherten Instanz selbst zu tun.

    
ptomli 18.10.2011, 14:51
quelle
7

Sie rufen concurrHashMap.put nach jedem Schreiben auf ein Objekt auf. Sie haben jedoch nicht angegeben, dass Sie vor jedem Lesevorgang auch concurrHashMap.get aufrufen. Dies ist notwendig.

Dies gilt für alle Formen der Synchronisation: Sie müssen einige "Checkpoints" in beiden Threads haben. Das Synchronisieren nur eines Threads ist nutzlos.

Ich habe den Quellcode von ConcurrentHashMap nicht überprüft, um sicherzustellen, dass put und get einen Vorfall auslösen, aber es ist nur logisch, dass sie das sollten.

Es gibt jedoch weiterhin ein Problem mit Ihrer Methode, selbst wenn Sie sowohl put als auch get verwenden. Das Problem tritt auf, wenn Sie ein Objekt ändern und es von einem anderen Thread (in einem inkonsistenten Zustand) verwendet wird, bevor es put ist. Es ist ein subtiles Problem, weil Sie denken könnten, der alte Wert würde gelesen werden, da er noch nicht put war und kein Problem verursachen würde. Das Problem ist, dass Sie nicht immer ein konsistentes älteres Objekt erhalten, wenn Sie nicht synchronisieren, sondern dass das Verhalten nicht definiert ist. Die JVM kann den Teil des Objekts in den anderen Threads jederzeit aktualisieren. Nur wenn Sie eine explizite Synchronisation verwenden, können Sie sicher sein, dass Sie die Werte konsistent über Threads aktualisieren.

Was Sie tun könnten:
(1) Synchronisieren Sie alle Zugriffe (Getter und Setter) auf Ihre Objekte überall im Code. Seien Sie vorsichtig mit den Setter: Stellen Sie sicher, dass Sie das Objekt nicht in einem inkonsistenten Zustand setzen können. Wenn Sie beispielsweise den Vornamen und den Nachnamen festlegen, reicht es nicht aus, zwei synchronisierte Setter zu verwenden: Sie müssen die Objektsperre für beide Operationen zusammen abrufen.
oder
(2) Wenn Sie put für ein Objekt in der Karte verwenden, legen Sie eine tiefe Kopie anstelle des Objekts selbst an. Auf diese Weise werden die anderen Threads niemals ein Objekt in einem inkonsistenten Zustand lesen.

BEARBEITEN :
Ich habe es gerade bemerkt

  

Gegenwärtig hat der Code des Objekts Synchronisation auf den "Setter"   (bewacht vom Objekt selbst). Es gibt keine Synchronisation auf dem   "Getters" noch sind die Mitglieder volatil.

Das ist nicht gut. Wie ich oben gesagt habe, ist die Synchronisierung auf nur einem Thread überhaupt keine Synchronisation. Sie könnten alle Ihre Writer-Threads synchronisieren, aber wen interessiert das, da die Leser nicht die richtigen Werte erhalten.

    
toto2 18.10.2011 15:11
quelle
5

Ich denke, dies wurde bereits in einigen Antworten gesagt, aber um es zusammenzufassen

Wenn Ihr Code geht

  • CHM # erhalten
  • ruft verschiedene Setter
  • auf
  • CHM # setzen

dann wird das "kommt-vor", das von dem Put bereitgestellt wird, garantieren, dass alle mutierten Aufrufe vor dem Setzen ausgeführt werden. Dies bedeutet, dass jedes nachfolgende get garantiert wird, diese Änderungen zu sehen.

Ihr Problem ist, dass der tatsächliche Zustand des Objekts nicht deterministisch ist, weil der tatsächliche Ablauf von Ereignissen

ist
  • Thread 1: CHM # erhalten
  • Thread 1: Aufruf-Setter
  • Thread 2: CHM # get
  • Thread 1: Aufruf-Setter
  • Thread 1: Aufruf-Setter
  • Teil 1: CHM # put

dann gibt es keine Garantie dafür, wie der Zustand des Objekts in Thread 2 sein wird. Es könnte das Objekt mit dem vom ersten Setter bereitgestellten Wert sehen oder nicht.

Die unveränderliche Kopie wäre der beste Ansatz, da dann nur vollständig konsistente Objekte veröffentlicht werden. Wenn Sie die verschiedenen Setter synchronisieren (oder die zugrunde liegenden Referenzen flüchtig), können Sie immer noch keinen konsistenten Zustand veröffentlichen, es bedeutet nur, dass das Objekt immer den neuesten Wert für jeden Getter bei jedem Aufruf sieht.

    
Matt 18.10.2011 16:37
quelle
3

Mein Verständnis ist, dass es für alle gets nach der Wiederherstellung funktioniert, aber dies wäre eine sehr unsichere Methode der Synchronisation.

Was passiert, passiert vor dem Rendern, aber während die Änderungen stattfinden. Möglicherweise sehen sie nur einige der Änderungen und das Objekt hat einen inkonsistenten Zustand.

Wenn Sie können, würde ich unveränderliche Objekte in der Map empfehlen. Dann wird jeder get eine Version des Objekts abrufen, die aktuell war, als es get.

    
Michael Krussel 18.10.2011 15:02
quelle
2

Dies ist ein Code-Snippet von java.util.concurrent.ConcurrentHashMap (Öffnen Sie JDK 7):

%Vor%

UNSAFE.getObjectVolatile() ist dokumentiert als Getter mit interner volatile Semantik, also wird die Speicherbarriere beim Abrufen der Referenz überschritten.

    
b_erb 18.10.2011 16:00
quelle
1

Ja, put verursacht einen flüchtigen Schreibvorgang, selbst wenn der Schlüsselwert bereits in der Map vorhanden ist.

Mit ConcurrentHashMap, um Objekte über Thread hinweg zu veröffentlichen, ist ziemlich effecient. Objekte sollten nicht mehr geändert werden, sobald sie sich in der Karte befinden. (Sie müssen nicht streng unveränderlich sein (mit letzten Feldern))

    
irreputable 18.10.2011 15:26
quelle