2009-10-29 19 views
5

Jestem w następującej sytuacji.Jak korzystać z ReadWriteLock?

Podczas uruchamiania aplikacji internetowej muszę załadować mapę, która jest następnie używana przez wiele wątków przychodzących. Oznacza to, że żądania przychodzą, a mapa służy do sprawdzenia, czy zawiera konkretny klucz, a jeśli tak, to wartość (obiekt) jest pobierana i powiązana z innym obiektem.

Teraz czasami zmienia się zawartość mapy. Nie chcę ponownie uruchamiać aplikacji, aby ponownie załadować nową sytuację. Zamiast tego chcę to zrobić dynamicznie.

Jednak w momencie ponownego ładowania mapy (usuwania wszystkich elementów i zastępowania ich nowymi), wciąż będą pojawiać się żądania jednoczesnego odczytu na tej mapie.

Co należy zrobić, aby uniemożliwić wszystkim odczytanym wątkom dostęp do tej mapy podczas jej ponownego ładowania? Jak mogę to zrobić w najbardziej wydajny sposób, ponieważ potrzebuję tego tylko przy przeładowywaniu mapy, która ma miejsce tylko sporadycznie (co każde x tygodni)?

Jeśli powyższe opcje nie są opcją (blokowanie), w jaki sposób mogę się upewnić, że podczas ponownego ładowania moje żądanie odczytu nie ucierpi z powodu nieoczekiwanych wyjątków (ponieważ klucz już tam nie jest lub wartość nie jest już obecna lub jest ponownie załadowany)?

Dostałem radę, że ReadWriteLock może mi pomóc. Czy możesz mi podać przykład, w jaki sposób powinienem używać tego ReadWriteLock z moimi czytelnikami i moim pisarzem?

Dzięki,
E

Odpowiedz

6

radzę sobie z tym poradzić, jak następuje:

  1. Wyraź swoją map dostępne w centralnym miejscu (może być pojedyncza Wiosna, statyczna ...).
  2. Po rozpoczęciu przeładowania niech instancja, jak jest, pracuje w innej instancji mapy.
  3. Po wypełnieniu nowej mapy zastąp starą mapę nową (to operacja atomowa).

Przykładowy kod:

static volatile Map<U, V> map = ....; 

    // ************************** 

    Map<U, V> tempMap = new ...; 
    load(tempMap); 
    map = tempMap; 

Efekty współbieżność:

  • volatile pomaga widoczności zmiennej do innych wątków.
  • Podczas przeładowywania mapy wszystkie pozostałe wątki widzą starą wartość bez przeszkód, więc nie ponoszą żadnej kary.
  • Każdy wątek, który pobiera mapę natychmiast po jej zmianie, będzie działał ze starymi wartościami.
    • Może poprosić kilka osób o to samo stare wystąpienie mapy, co jest świetne dla spójności danych (nie ładowanie pierwszej wartości ze starszej mapy i innych z nowszej).
    • Kończy przetwarzanie żądania ze starą mapą, ale następne żądanie ponownie poprosi mapę i otrzyma nowsze wartości.
+0

Byłoby OK pod warunkiem, że nie trzeba coś sprawdzić na mapie przed aktualizacją to, co jest operacją sprawdzania, a następnie modyfikacji, a zatem nie atomową. – Adam

3

Jeśli gwinty klienckie nie modyfikować mapę, czyli zawartość mapie jest wyłącznie zależny od źródła, z którego jest on załadowany, można po prostu załadować nową mapę i zastąpić odniesienie do mapuj, że wątki Twojego klienta są używane po załadowaniu nowej mapy.

Inne, które używają dwukrotnie więcej pamięci przez krótki czas, nie poniosą kary za wydajność.

Jeśli na mapie jest za dużo pamięci, aby mieć 2 z nich, można użyć tej samej taktyki na obiekt na mapie; wykonaj iterację na mapie, skonstruuj nowy zmapowany obiekt i zastąp oryginalne odwzorowanie po załadowaniu obiektu.

+0

Wykonanie tego na podstawie obiektu spowoduje pozostawienie mapy w stanie mieszanym, chociaż --- niektóre obiekty będą ze starej mapy, niektóre z nowych. Czy to ma znaczenie zależy od przypadku użycia. – uckelman

2

Należy pamiętać, że zmieniając odniesienie sugerowane przez innych może powodować problemy, jeśli opierają się na mapie jako niezmieniony przez jakiś czas (np if (map.contains(key)) {V value = map.get(key); ...} Jeśli trzeba, że ​​należy zachować lokalną odniesienie do mapy.

static Map<U,V> map = ...; 

void do() { 
    Map<U,V> local = map; 
    if (local.contains(key)) { 
    V value = local.get(key); 
    ... 
    } 
} 

EDIT:

założenie jest takie, że nie chce kosztownej synchronizacji dla wątków klienckich Jako kompromis, pozwalasz wątki klienckie, aby zakończyć swoją pracę, że już rozpoczął zanim map zmieniło -. ignorując wszelkie zmiany na mapie, które zdarzyły się podczas działania, w ten sposób możesz bezpiecznie zrobić jakąś dupę informacje o Twojej mapie - np. że klucz jest obecny i zawsze mapowany na tę samą wartość przez czas trwania pojedynczego żądania. W powyższym przykładzie, jeśli wątek czytnika zmieni mapę tuż po kliencie o nazwie map.contains(key), klient może uzyskać zerową wartość na map.get(key) - a prawie na pewno zakończy się to żądanie z wyjątkiem NullPointerException. Jeśli więc robisz wiele odczytów na mapie i musisz zrobić pewne założenia, jak wspomniano wcześniej, najłatwiej zachować lokalne odniesienie do (być może przestarzałej) mapy.

Zmienne słowo kluczowe nie jest tutaj bezwzględnie konieczne. Po prostu upewni się, że nowa mapa jest używana przez inne wątki, gdy tylko zmienisz odniesienie (map = newMap). Bez lotności następny odczyt (local = map) może przez jakiś czas przywracać dawne odniesienie (mówimy jednak o mniej niż nanosekundach) - szczególnie w systemach wielordzeniowych, o ile dobrze pamiętam. Nie przejmowałbym się tym, ale czujesz potrzebę dodatkowego kawałka wielowątkowego piękna, oczywiście, że możesz go używać;)

+0

Czy masz na myśli mapę lotną statyczną map = ..; czy to nie ma znaczenia? Kiedy mój mapreloader (inny wątek) robi map = "nowo utworzona mapa", podczas gdy mój wątek czytelnika uzyskuje dostęp do lokalnego odnośnika tej mapy, mój czytelnik nie zdaje sobie z tego sprawy i zachowuje dostęp do "starej" mapy dopóki nie wykona polecenia local = mapę? Czy to jest poprawne ? – EdwinDhondt

+0

zobacz moją edycję powyżej – sfussenegger

+0

Ale w obu podejściach lokalnych = map (z lub bez lotnych) nigdy nie dostanę nullpointerexceptions, tylko być może niektóre nieaktualne dane? Czy to prawda? – EdwinDhondt

2

Bardzo lubię niestabilne rozwiązanie mapowe od KLE i pójdę z tym. Innym pomysłem, który może być interesujący dla użytkownika, jest użycie odpowiednika mapy CopyOnWriteArrayList, w zasadzie CopyOnWriteMap. Zbudowaliśmy jeden z nich wewnętrznie i to nietrywialne, ale może być w stanie znaleźć COWMap w środowisku naturalnym:

http://old.nabble.com/CopyOnWriteMap-implementation-td13018855.html

1

Jest to odpowiedź od Javadocs JDK dla ReentrantReadWriteLock implementation of ReadWriteLock. Kilka lat późno, ale nadal ważne, zwłaszcza jeśli nie chcesz polegać wyłącznie na volatile

class RWDictionary { 
    private final Map<String, Data> m = new TreeMap<String, Data>(); 
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
    private final Lock r = rwl.readLock(); 
    private final Lock w = rwl.writeLock(); 

    public Data get(String key) { 
     r.lock(); 
     try { return m.get(key); } 
     finally { r.unlock(); } 
    } 
    public String[] allKeys() { 
     r.lock(); 
     try { return m.keySet().toArray(); } 
     finally { r.unlock(); } 
    } 
    public Data put(String key, Data value) { 
     w.lock(); 
     try { return m.put(key, value); } 
     finally { w.unlock(); } 
    } 
    public void clear() { 
     w.lock(); 
     try { m.clear(); } 
     finally { w.unlock(); } 
    } 
}