Möglichkeiten, diesen Code mit nur von JDK (6) bereitgestellten Klassen zu verbessern? (Nebenläufigkeit, Fadensicherheit)

8

(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:

%Vor%

Im Moment ist die Logik des Codes wie folgt:

  • Wenn die Nachrichtenquelle für dieses Gebietsschema bereits erfolgreich gesucht wurde, wird
  • zurückgegeben
  • Von diesem Zeitpunkt an wurde keine Suche mehr durchgeführt. Es ist jedoch unbekannt, ob dies bedeutet, dass ein vorheriger Lookup-Versuch durchgeführt wurde: Überprüfen des Satzes fehlgeschlagener Lookups, wenn das Locale, das gesucht werden soll, ein bekannter Fehler ist, Rückgabe der leeren Liste;
  • Nun ist die bekannte Situation, dass diese Locale-Suche tatsächlich nie durchgeführt wurde: Führen Sie sie aus; Abhängig vom Ergebnis der Methode 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.

%Vor%

Meine Frage hier ist dreifach:

  • Ich beschränke mich absichtlich auf Klassen, die nur für das JDK verfügbar sind; Ich wählte 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?
  • Ich denke dieser Code ist eventuell threadsicher; einige Eckfälle können dazu führen, dass Nachforschungen mehr als einmal durchgeführt werden, aber das ist der Grund, warum ich .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?
  • Dieser Code hat einen fatalen Fehler: mehr als ein Thread führt möglicherweise gleichzeitig tryAndLookup() aus ... Welche Lösungen gibt es, um diese Methode nur einmal pro Suche ausführen zu lassen?
fge 01.06.2013, 21:24
quelle

3 Antworten

0

Antwort auf Selbst ...

Der Algorithmus wurde komplett überarbeitet. Es basiert auf der FutureTask des JDK, da es zwei hat sehr schöne Eigenschaften:

  • Die Methode .run() ist asynchron;
  • es ist zustandsbehaftet, sowohl beim Erfolg als auch beim Scheitern - das heißt, es wird das bereits berechnete Ergebnis oder die geworfene Ausnahme (in ExecutionException eingeschlossen) auf .get() zurückgeben.

Dies wirkt sich auf die verwendeten Datenstrukturen aus:

  • die zuvor vorhandene lookupFailures Menge, die Fehler aufgezeichnet hat, ist verschwunden;
  • die vorhandene Karte, die ein 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:

  • Sperren Sie die Karte;
  • lese die Aufgabe für das Gebietsschema; Wenn es noch nicht vorhanden war, erstellen Sie es und .run() it;
  • entsperren Sie die Karte;
  • .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() ).

    
fge 02.06.2013, 02:37
quelle
1

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

%Vor%     
Zim-Zam O'Pootertoot 01.06.2013 21:35
quelle
1

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.

    
flup 01.06.2013 23:43
quelle

Tags und Links