6

Zurück zur Nebenläufigkeit. Inzwischen ist klar, dass für die double checked locking zu arbeiten die Variable muss als volatile deklariert werden. Aber was ist dann, wenn eine doppelt überprüfte Verriegelung wie unten verwendet wird.Double checked locking mit regulärer HashMap

class Test<A, B> { 

    private final Map<A, B> map = new HashMap<>(); 

    public B fetch(A key, Function<A, B> loader) { 
     B value = map.get(key); 
     if (value == null) { 
      synchronized (this) { 
       value = map.get(key); 
       if (value == null) { 
        value = loader.apply(key); 
        map.put(key, value); 
       } 
      } 
     } 
     return value; 
    } 

} 

Warum muss es wirklich ein ConcurrentHashMap und keine regulären HashMap sein? Alle Kartenänderungen werden innerhalb des Blocks synchronized durchgeführt, und der Code verwendet keine Iteratoren, daher sollte es technisch keine Probleme mit "gleichzeitiger Änderung" geben.

Bitte vermeiden Sie was auf die Verwendung von putIfAbsent/computeIfAbsent als ich über das Konzept gefragt bin und nicht die Verwendung von API :), es sei denn mit dieser API trägt zum HashMap vs ConcurrentHashMap Thema.

-Update 2016-12-30

Diese Frage durch einen Kommentar unten von Holger "HashMap.get die Struktur ändern, aber Ihr Aufruf von put nicht tut beantwortet wurde. Da es ein Aufruf von get außen des synchronisierten Blocks kann er einen unvollständigen Zustand einer put Operation gleichzeitig sehen. " Vielen Dank!

+1

Da die Karte wird nach einigen Puts Größe ändern und es wird sicherlich Ihre entsperrt bekommen Anrufe zu brechen. –

+5

putIfAbsent/computeIfAbsent sind atomare und nicht nur für die Bequemlichkeit –

+0

Thomas, könnten Sie bitte auf "Break Get Calls" bitte. Ich habe nur explizite Verweise auf gleichzeitige Puts und Iteratoren in den Dokumenten gefunden und bei der Suche nach "hashmap concurrent access" –

Antwort

15

Diese Frage ist in so vielen Punkten verworren, dass es schwer zu beantworten ist.

Wenn dieser Code immer nur aus einem einzigen Thread aufgerufen wird, dann machen Sie es zu kompliziert; Sie brauchen keine Synchronisation. Aber klar ist das nicht deine Absicht.

So rufen mehrere Threads die Methode fetch auf, die ohne Synchronisierung an HashMap.get() delegiert. HashMap ist nicht Thread-sicher. Bam, Ende der Geschichte. Ist es nicht einmal wichtig, wenn Sie versuchen, doppeltes Sperren zu simulieren; Die Realität ist, dass das Aufrufen von get() und put() auf einer Karte die internen veränderbaren Datenstrukturen der HashMap ohne konsistente Synchronisation auf allen Codepfaden manipulieren, und da Sie diese gleichzeitig von mehreren Threads aufrufen können, sind Sie bereits tot.

(Auch denken Sie wahrscheinlich, dass HashMap.get() eine reine Leseoperation ist, aber das ist auch falsch. Was ist, wenn die HashMap tatsächlich eine LinkedHashMap ist (was eine Unterklasse von HashMap ist). LinkedHashMap.get() wird die Zugriffsreihenfolge aktualisieren Dazu gehört das Schreiben in interne Datenstrukturen - hier gleichzeitig ohne Synchronisation. Aber auch wenn get() nicht schreibt, ist Ihr Code hier immer noch kaputt.)

Faustregel: wenn Sie denken, Sie haben einen cleveren Trick, mit dem Sie die Synchronisierung vermeiden können, sind Sie fast sicher falsch.

+0

würde der zweite Prüfwert == null vom Compiler verworfen werden? –

+0

Danke für Ihre Antwort. Es erklärt nicht nur das Problem, sondern liefert auch eine wertvolle Information über Implementierungsdetails der 'get()' Methode. – AlexR

+0

Brian, ich stimme der ersten Hälfte der Antwort zu. Der Vertrag besagt, dass HashMap nicht synchronisiert ist und als solches verwendet werden sollte. Die zweite Hälfte ist falsch, weil die Frage konkrete Klassen auflistet. Und da Sie die Implementierung erwähnt haben, ändert HashMap.get die angegebene Struktur in der aktuellen Java7/8-Implementierung nicht. Und wieder stimme ich der ersten Hälfte zu, und ich würde es als die Antwort akzeptiert haben, wenn es eine maßgebende Referenz gab, die HashMap.get –