Как использовать ReadWriteLock? - PullRequest
5 голосов
/ 29 октября 2009

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

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

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

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

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

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

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

Спасибо
E

Ответы [ 5 ]

6 голосов
/ 29 октября 2009

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

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

Пример кода:

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

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

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

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

  • volatile помогает с видимостью переменной для других потоков.
  • При перезагрузке карты все остальные потоки видят старое значение без изменений, поэтому они не получают никакого штрафа.
  • Любой поток, извлекающий карту за мгновение до ее изменения, будет работать со старыми значениями.
    • Он может запросить несколько обращений к одному и тому же экземпляру старой карты, что отлично подходит для согласованности данных (не загружая первое значение из старой карты и другие из более новой).
    • Он завершит обработку своего запроса со старой картой, но следующий запрос снова запросит карту и получит более новые значения.
3 голосов
/ 29 октября 2009

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

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

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

2 голосов
/ 09 ноября 2009

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

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

2 голосов
/ 29 октября 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). Без энергозависимости последующее чтение (local = map) могло бы все еще вернуть старую ссылку в течение некоторого времени (хотя мы говорим о менее чем наносекунде) - особенно в многоядерных системах, если я правильно помню. Мне было бы все равно, но если вы чувствуете необходимость в этой дополнительной красоте многопоточности, вы можете свободно использовать ее;)

1 голос
/ 03 февраля 2015

Это ответ 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(); }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...