Verursacht das Umbau eines Objekts in eine Concurrenthashmap eine Speicherbeziehung „passiert vor“?

StackOverflow https://stackoverflow.com/questions/7809131

Frage

Ich arbeite mit vorhandenen Code mit einem Objektspeicher in Form einer Concurrenthashmap. In der Karte werden veränderliche Objekte gespeichert, die von mehreren Threads verwendet werden. Keine zwei Threads versuchen, ein Objekt gleichzeitig durch Design zu ändern. Mein Anliegen ist die Sichtbarkeit der Modifikationen zwischen den Threads.

Derzeit hat der Code der Objekte die Synchronisation auf den "Settern" (vom Objekt selbst bewacht). Es gibt keine Synchronisation an den "Gettern" und sind auch die Mitglieder volatil. Für mich würde dies bedeuten, dass die Sichtbarkeit nicht garantiert ist. Wenn jedoch ein Objekt geändert wird, ist es jedoch Put-Put Zurück in die Karte (die put() Methode wird erneut aufgerufen, gleicher Schlüssel). Bedeutet dies, dass ein anderer Thread das Objekt aus der Karte herauszieht, die Änderungen angezeigt werden?

Ich habe dies hier auf Stackoverflow, IN recherchiert, in JCIP, und in der Paketbeschreibung für java.util.concurrent. Ich habe mich im Grunde genommen verwirrt, denke ich ... aber der letzte Strohhalm, der mich dazu veranlasste, diese Frage zu stellen, stammte aus der Paketbeschreibung, sodass es heißt:

Aktionen in einem Thread vor dem Platzieren eines Objekts in eine gleichzeitige Sammlung, die vor dem Zugriff oder Entfernen dieses Elements aus der Sammlung in einem anderen Thread vorhanden ist.

Enthält in Bezug auf meine Frage "Aktionen" die Änderungen an den in der Karte gespeicherten Objekten vor dem RE-Put ()? Wenn all dies zu einer Sichtbarkeit über die Threads hinweg führt, ist dies dann ein effizienter Ansatz? Ich bin relativ neu in Threads und würde mich über Ihre Kommentare freuen.

Bearbeiten:

Vielen Dank für Ihre Antworten! Dies war meine erste Frage zu Stackoverflow und es war mir sehr hilfreich.

Ich muss mit gehen ptomliDie Antwort, weil ich denke, dass es am deutlichsten meine Verwirrung angesprochen hat. Um eine "geschehene" -Revel-Beziehung aufzubauen, wirkt sich in diesem Fall nicht unbedingt die Änderungssichtbarkeit aus. Meine "Titelfrage" ist in Bezug auf meine im Text beschriebene tatsächliche Frage schlecht konstruiert. ptomli'S Antwort JETZT JIVES mit dem, was ich gelesen habe JCIP: "Um sicherzustellen, dass alle Threads die aktuellsten Werte von gemeinsam genutzten veränderlichen Variablen sehen, müssen die Lesen- und Schreiben von Threads auf einer gemeinsamen Sperre synchronisieren" (Seite 37). Das Wiederieren des Objekts in die Karte gibt diese gemeinsame Sperre für die Änderung der Mitglieder des eingefügten Objekts nicht an.

Ich schätze alle Tipps für Veränderungen (unveränderliche Objekte usw.), und ich stimme voll und ganz zu. Aber für diesen Fall gibt es, wie ich bereits erwähnt habe, aufgrund der sorgfältigen Handhabung des Fädens keine gleichzeitige Änderung. Ein Thread modifiziert ein Objekt, und ein weiterer Thread liest später das Objekt (wobei das CHM der Objektförderer ist). Ich denke, das CHM reicht nicht aus, um sicherzustellen, dass der später ausführende Thread die Änderungen aus der ersten angegebenen Situation feststellt, die ich zur Verfügung gestellt habe. Ich denke jedoch, dass viele von Ihnen Die Titelfrage korrekt beantwortet.

War es hilfreich?

Lösung

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

Wenn die Fälle, die Sie auf der Karte speichern, synchronisierte Mutatoren haben, aber keine synchronisierten Zubehör, dann sehe ich nicht, wie sie wie beschrieben fadenfaden werden können.

Nehmen Sie die Map Aus der Gleichung heraus und bestimmen Sie, ob die von Ihnen gespeicherten Fälle für sich selbst sicher sind.

Wenn jedoch ein Objekt geändert wird, wird es wieder in die Karte zurückgeschnitten (die Methode put () wird erneut aufgerufen, gleicher Schlüssel). Bedeutet dies, dass ein anderer Thread das Objekt aus der Karte herauszieht, die Änderungen angezeigt werden?

Dies veranschaulicht die Verwirrung. Die Instanz, die in die Karte übernommen wird, wird von einem anderen Thread aus der Karte abgerufen. Dies ist die Garantie der gleichzeitigen Karte. Das hat nichts mit der Sichtbarkeit des Zustands der gespeicherten Instanz selbst zu tun.

Andere Tipps

Du rufst an concurrHashMap.put Nach jedem Schreiben in ein Objekt. Sie haben jedoch nicht angegeben, dass Sie auch anrufen concurrHashMap.get Vor jedem Lesen. Das ist notwendig.

Dies gilt für alle Formen der Synchronisation: Sie müssen einige "Checkpoints" in beiden Threads haben. Die Synchronisierung nur ein Thread ist nutzlos.

Ich habe den Quellcode von ConcurrenthasMap nicht überprüft, um dies sicherzustellen put und get Trigger ein passiert-vor, aber es ist nur logisch, dass sie es sollten.

Es gibt jedoch noch ein Problem mit Ihrer Methode, auch wenn Sie beide verwenden put und get. Das Problem tritt auf, wenn Sie ein Objekt ändern und es (in einem inkonsistenten Zustand) durch den anderen Thread verwendet wird, bevor es ist put. Es ist ein subtiles Problem, weil Sie vielleicht denken, dass der alte Wert gelesen wird, da dies nicht der Fall war put Trotzdem würde es kein Problem verursachen. Das Problem ist, dass Sie, wenn Sie nicht synchronisieren, nicht garantiert ein konsistentes älteres Objekt erhalten, sondern das Verhalten nicht definiert ist. Das JVM kann jederzeit den Teil des Objekts in den anderen Threads aktualisieren. Erst wenn Sie eine explizite Synchronisation verwenden, sind Sie sicher, dass Sie die Werte auf konsistente Weise über Threads hinweg aktualisieren.

Was Sie tun könnten:
(1) Synchronisieren Sie alle Zugriffe (Getter und Setter) überall im Code mit Ihren Objekten. Seien Sie vorsichtig mit den Setzen: Stellen Sie sicher, dass Sie das Objekt nicht in einem inkonsistenten Zustand einstellen können. Wenn Sie beispielsweise den ersten und Nachnamen festlegen, reicht zwei synchronisierte Setter nicht aus: Sie müssen die Objektschloss für beide Vorgänge zusammenstellen.
oder
(2) Wenn Sie put Ein Objekt in der Karte, geben Sie eine tiefe Kopie anstelle des Objekts selbst ein. Auf diese Weise werden die anderen Threads niemals ein Objekt in einem inkonsistenten Zustand lesen.

BEARBEITEN:
mir ist eben grade aufgefallen

Derzeit hat der Code der Objekte die Synchronisation auf den "Settern" (vom Objekt selbst bewacht). Es gibt keine Synchronisation an den "Gettern" und sind auch die Mitglieder volatil.

Das ist nicht gut. Wie ich bereits sagte, ist die Synchronisierung nur mit einem Thread überhaupt keine Synchronisation. Sie können alle Ihre Schriftstellerfäden synchronisieren, aber wen interessiert es, da die Leser nicht die richtigen Werte erhalten.

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

Wenn Ihr Code geht

  • CHM#GET
  • Rufen Sie verschiedene Setzer an
  • CHM#Put

Dann garantiert der von dem Put bereitgestellte "passiert vor", dass alle Mutate-Anrufe vor dem Put ausgeführt werden. Dies bedeutet, dass jede nachfolgende GET garantiert diese Änderungen feststellt.

Ihr Problem ist, dass der tatsächliche Zustand des Objekts nicht deterministisch ist, da der tatsächliche Ereignisfluss ist

  • Thread 1: CHM#GET
  • Thread 1: Rufen Sie Setter auf
  • Thread 2: CHM#GET
  • Thread 1: Rufen Sie Setter auf
  • Thread 1: Rufen Sie Setter auf
  • Thread 1: CHM#

Dann gibt es keine Garantie darüber, wie sich der Zustand des Objekts in Thread 2 befindet. Es kann das Objekt mit dem vom ersten Setter bereitgestellten Wert sehen, oder dies kann nicht sein.

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

Mein Verständnis ist, dass es für alle nach dem Umschlag funktionieren sollte, aber dies wäre eine sehr unsichere Methode der Synchronisation.

Was passiert, wenn das vor dem Umschlag passiert, aber während Modifikationen stattfinden. Sie können nur einige der Änderungen sehen, und das Objekt hätte einen inkonsistenten Zustand.

Wenn Sie können, würde ich empfehlen, unveränderliche Objekte auf der Karte zu speichern. Dann wird jeder Get eine Version des Objekts abrufen, die aktuell war, als es das Get erstellte.

Das ist ein Codeausschnitt von java.util.concurrent.ConcurrentHashMap (Öffnen Sie JDK 7):

  919       public V get(Object key) {
  920           Segment<K,V> s; // manually integrate access methods to reduce overhead
  921           HashEntry<K,V>[] tab;
  922           int h = hash(key.hashCode());
  923           long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
  924           if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
  925               (tab = s.table) != null) {
  926               for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
  927                        (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
  928                    e != null; e = e.next) {
  929                   K k;
  930                   if ((k = e.key) == key || (e.hash == h && key.equals(k)))
  931                       return e.value;
  932               }
  933           }
  934           return null;
  935       }

UNSAFE.getObjectVolatile() ist dokumentiert als Getter mit intern volatile Semantik, somit wird die Gedächtnisbarriere überquert, wenn die Referenz abgerufen wird.

Jawohl, put verursacht ein volatiles Schreiben, auch wenn der Schlüsselwert bereits in der Karte vorhanden ist.

Die Verwendung von Concurrenthashmap zur Veröffentlichung von Objekten über Thread ist ziemlich wirksam. Objekte sollten nicht weiter geändert werden, wenn sie in der Karte sind. (Sie müssen nicht streng sein unveränderlich (mit endgültigen Feldern)))

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top