Как использовать ReadWriteLock?

У меня следующая ситуация.

При запуске веб-приложения мне нужно загрузить карту, которая затем используется несколькими входящими потоками. То есть приходят запросы, и карта используется, чтобы узнать, содержит ли она конкретный ключ, и если да, то значение (объект) извлекается и связывается с другим объектом.

Теперь время от времени содержимое Карты меняется. Я не хочу перезапускать свое приложение, чтобы перезагрузить новую ситуацию. Вместо этого я хочу сделать это динамически.

Однако во время повторной загрузки карты (удаление всех элементов и замена их новыми) одновременные запросы на чтение на этой карте все еще поступают.

Что я должен сделать, чтобы предотвратить доступ всех потоков чтения к этой карте во время ее перезагрузки? Как я могу сделать это наиболее эффективным способом, потому что мне это нужно только при перезагрузке карты, которая будет происходить спорадически (каждый раз в x недель)?

Если это не вариант (блокировка), как я могу убедиться, что при перезагрузке мой запрос на чтение не будет страдать от неожиданных исключений (потому что ключа больше нет, или значение больше не присутствует или перезагружается)?

Мне дали совет, что ReadWriteLock может мне помочь. Можете ли вы дать мне пример того, как я должен использовать этот ReadWriteLock с моими читателями и моим писателем?

Спасибо,
Е


person EdwinDhondt    schedule 29.10.2009    source источник


Ответы (5)


Я предлагаю справиться с этим следующим образом:

  1. Сделайте свою карту доступной в центральном месте (может быть синглтоном Spring, статическим...).
  2. При запуске перезагрузки пусть экземпляр как есть работает в другом экземпляре карты.
  3. Когда эта новая карта будет заполнена, замените старую карту этой новой (это атомарная операция).

Образец кода:

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

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

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

Эффекты параллелизма:

  • volatile помогает с видимостью переменной для других потоков.
  • При перезагрузке карты все остальные потоки видят старое значение нетронутым, поэтому они не получают никаких штрафов.
  • Any thread that retrieves the map the instant before it is changed will work with the old values.
    • It can ask several gets to the same old map instance, which is great for data consistency (not loading the first value from the older map, and others from the newer).
    • Он завершит обработку своего запроса со старой картой, но следующий запрос снова запросит карту и получит более новые значения.
person KLE    schedule 29.10.2009
comment
Это было бы нормально при условии, что вам не нужно проверять что-то на карте перед ее обновлением, что является операцией проверки, а затем изменения и, следовательно, не атомарной. - person Adam; 03.02.2015

Если клиентские потоки не изменяют карту, т. е. содержимое карты зависит исключительно от источника, из которого она загружена, вы можете просто загрузить новую карту и заменить ссылку на карту, которую ваши клиентские потоки используют, как только новая карта загружена.

Кроме использования памяти в два раза в течение короткого времени, производительность не снижается.

В случае, если карта использует слишком много памяти, чтобы иметь 2 из них, вы можете использовать ту же тактику для каждого объекта на карте; перебирать карту, создавать новый сопоставленный объект и заменять исходное сопоставление после загрузки объекта.

person rsp    schedule 29.10.2009
comment
Выполнение этого для каждого объекта оставит карту в смешанном состоянии, хотя некоторые объекты будут из старой карты, некоторые из новой. Имеет ли это значение, зависит от варианта использования. - person uckelman; 29.10.2009

Обратите внимание, что изменение ссылки, предложенной другими, может вызвать проблемы, если вы полагаетесь на то, что карта не изменится какое-то время (например, if (map.contains(key)) {V value = map.get(key); ...}. Если вам это нужно, вы должны сохранить локальную ссылку на карту:

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

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

РЕДАКТИРОВАТЬ:

Предполагается, что вам не нужна дорогостоящая синхронизация для ваших клиентских потоков. В качестве компромисса вы позволяете клиентским потокам завершить свою работу, которую они уже начали до изменения вашей карты, игнорируя любые изменения карты, которые произошли во время ее работы. Таким образом, вы можете безопасно сделать некоторые предположения о вашей карте, например. что ключ присутствует и всегда сопоставляется с одним и тем же значением в течение одного запроса. В приведенном выше примере, если ваш поток чтения изменил карту сразу после того, как клиент с именем map.contains(key), клиент может получить null на map.get(key), и вы почти наверняка завершите этот запрос с NullPointerException. Поэтому, если вы выполняете многократное чтение карты и вам нужно сделать некоторые предположения, как упоминалось ранее, проще всего сохранить локальную ссылку на (возможно, устаревшую) карту.

Ключевое слово volatile здесь не является строго необходимым. Это просто гарантирует, что новая карта будет использоваться другими потоками, как только вы измените ссылку (map = newMap). Без volatile последующее чтение (local = map) все еще может вернуть старую ссылку в течение некоторого времени (хотя мы говорим о менее чем наносекунде) - особенно в многоядерных системах, если я правильно помню. Меня бы это не заботило, но если вы чувствуете потребность в этой дополнительной многопоточной красоте, вы, конечно, можете ее использовать;)

person sfussenegger    schedule 29.10.2009
comment
Вы имеете в виду статическую ‹b›volatile Map‹U,V› map = ..;‹/b› или это не имеет значения? Поэтому, когда мой mapreloader (другой поток) делает map = new created map, в то время как мой поток чтения обращается к локальной ссылке этой карты, мой читатель не знает об этом и продолжает обращаться к старой карте, пока не выполнит local = map? Это правильно ? - person EdwinDhondt; 01.11.2009
comment
Но в обоих подходах local = map (с volatile или без него) я никогда не получу исключений nullpointer, только, возможно, некоторые устаревшие данные? Это правильно ? - person EdwinDhondt; 03.11.2009
comment
Вот так. Что касается volatile, вот два хороших ресурса: javamex.com/tutorials/synchronization_volatile.shtml и javamex.com/tutorials/synchronization_volatile_when.shtml - person sfussenegger; 03.11.2009
comment
Если мой поток чтения получает объект с карты (getObject(key)) и после получения (изменчивой) карты заменяется новой, я полагаю, что мой поток чтения все еще имеет действительную ссылку на объект, взятый из старой карты ? - person EdwinDhondt; 05.11.2009
comment
В Java у вас всегда будет действующая ссылка, независимо от того, что вы делаете. Таким образом, у вас также будет действующая ссылка на саму старую карту. В результате старая карта будет доступна для сборки мусора, как только закончатся все запросы, использующие ее (т.е. как только на нее не останется ссылок). - person sfussenegger; 05.11.2009

Мне очень нравится решение volatile Map от KLE, и я бы согласился с ним. Другая идея, которая может кому-то показаться интересной, заключается в использовании карты, эквивалентной CopyOnWriteArrayList, в основном CopyOnWriteMap. Мы построили один из них внутри компании, и это нетривиально, но вы можете найти COWMap в дикой природе:

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

person Alex Miller    schedule 09.11.2009

Это ответ из javadocs JDK для ReentrantReadWriteLock реализация ReadWriteLock. С опозданием на несколько лет, но все еще в силе, особенно если вы не хотите полагаться только на 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(); }
    }
}
person Adam    schedule 03.02.2015