(Vorbemerkung: vielleicht ist dies besser geeignet für codereview ?)
BEARBEITEN Antwort auf self ; Ich glaube, diese Antwort deckt alle meine Bedürfnisse / Probleme ab, aber Kommentare sind natürlich willkommen. Original-Frage links unten als Referenz.
Hallo,
Von Interesse ist hier die Methode .getSources()
. Diese Methode soll eine Liste von Nachrichtenquellen für eine gegebene Locale
zurückgeben.
Die beiden zentralen Datenstrukturen für diese Methode sind sources
und failedLookups
, siehe Code für Kommentare.
Diese spezielle Implementierung von .getSources()
kann immer nur eine leere Liste oder eine Liste mit einzelnen Elementen zurückgeben, abhängig von der Methode tryAndLookup()
, deren Prototyp ist:
Im Moment ist die Logik des Codes wie folgt:
tryAndLookup
, notieren Sie entweder einen Erfolg oder einen Fehler. Nun, warum gehe ich so weit: Ich habe keine Kontrolle über tryAndLookup()
; Es kann übermäßig lange dauern, bis eine gültige Quelle oder ein Fehler zurückgegeben wird. Daher zögere ich, eine grobe Sperre oder synchronized
Block zu verwenden.
Meine Frage hier ist dreifach:
ConcurrentHashMap
als ConcurrentMap
Implementierung und CopyOnWriteArraySet
als Thread-sichere Set
Implementierung; Aus dem Javadoc sind das die besten, die ich finden konnte. Aber wurde ich irgendwo irregeführt? .putIfAbsent()
; Im Moment habe ich immer Guavas LoadingCache
für Caching-Zwecke benutzt und ihm vertraut, und dies ist mein erster Ausflug aus diesem Gebiet; Ist dieser Code tatsächlich threadsicher? tryAndLookup()
aus ... Welche Lösungen gibt es, um diese Methode nur einmal pro Suche ausführen zu lassen? Antwort auf Selbst ...
Der Algorithmus wurde komplett überarbeitet. Es basiert auf der FutureTask des JDK, da es zwei hat sehr schöne Eigenschaften:
.run()
ist asynchron; ExecutionException
eingeschlossen) auf .get()
zurückgeben. Dies wirkt sich auf die verwendeten Datenstrukturen aus:
lookupFailures
Menge, die Fehler aufgezeichnet hat, ist verschwunden; ConcurrentMap
war, wird jetzt durch eine einfache Map
ersetzt, die von einem ReentrantLock
geschützt wird; Seine Werte sind jetzt FutureTask<MessageSource>
anstelle von MessageSource
. Dies hat auch ziemlich viel Einfluss auf den Algorithmus, der viel einfacher ist:
.run()
it; .get()
das Ergebnis: gibt eine einzelne Elementliste bei Erfolg zurück, eine leere Liste bei Fehler. Vollständiger Code mit Kommentaren:
%Vor% Ich könnte sogar die "einzelne erstellte Aufgabe" sowohl beim Nachschlagen als auch beim Scheitern testen, da tryAndLookup()
abstrakt, also "spionierbar" mit Mockito ist. Vollständige Testklassenquelle hier (Methoden onlyOneTaskIsCreatedPer*LocaleLookup()
).
Als Alternative zu CopyOnWriteArraySet
könnten Sie ConcurrentHashMap
mit sinnlosen Werten verwenden (zB verwenden Sie immer Boolean.TRUE
als Wert - Sie interessieren sich nur für die Schlüssel), oder Sie könnten ein ConcurrentSkipListSet , was im Wesentlichen eine gleichzeitige TreeSet
ist, die eine Skip-Liste anstelle von verwendet ein ausgewogener Binärbaum.
Nehmen wir an, dass tryAndLookup
schnell ist und keine Nebenwirkungen hat, ist es nicht wirklich wichtig, ob Sie es gelegentlich mehrmals ausführen, da Ihr "eventuell threadsicherer" Code threadsicher ist in dem Sinne, dass es kein anomales Verhalten hervorruft. (Wenn es langsam ist, kann es effizienter sein, Sperren zu verwenden, um sicherzustellen, dass Sie es so oft wie möglich ausführen, aber in diesem Fall wäre Ihr Code immer noch frei von anomalem Verhalten. Wenn es Nebenwirkungen hat, dann können Sie Haben Sie ein Datenrennen, wenn Sie es zweimal am selben Gebietsschema ausführen.)
Bearbeiten Da tryAndLookup
möglicherweise Nebenwirkungen hat, können Sie entweder auf locale
synchronisieren, oder Sie können Ihre Methode wie folgt ändern
Sie können die ersten beiden Schritte in einem synchronisierten Block mit einfacheren Datentypen ausführen.
Wenn Sie in diesen Schritten feststellen müssen, dass Sie tryAndLookup()
ausführen müssen, speichern Sie eine Future für das ausstehende Ergebnis in einer separaten Liste ausstehender Suchvorgänge, bevor der synchronisierte Block verlassen wird.
Dann außerhalb des synchronisierten Blocks die eigentliche Suche durchführen.
Threads, die feststellen, dass sie dasselbe Ergebnis benötigen, finden das Future und können get()
ihr Ergebnis außerhalb des synchronisierten Blocks finden.
Tags und Links java caching concurrency