необходимо ли понижение блокировки при использовании ReentrantReadWriteLock

В документе ReentrantReadWriteLock есть пример использования понижения блокировки (см. это).

class CachedData {
    final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    Object data;
    volatile boolean cacheValid;

    void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // Recheck state because another thread might have
                // acquired write lock and changed state before we did.
                if (!cacheValid) {
                    data = ...
                    cacheValid = true;
                }
                // Downgrade by acquiring read lock before releasing write lock
                rwl.readLock().lock();//B
            } finally {//A
                rwl.writeLock().unlock(); // Unlock write, still hold read
            }
        }
        try {
            use(data);
        } finally {//C
            rwl.readLock().unlock();
        }
    }
}

Если я изменю Object data на volatile Object data, нужно ли мне по-прежнему понижать уровень блокировки записи до блокировки чтения?


Обновить

Я имею в виду, что если я добавлю volatile к data, прежде чем снять блокировку записи в блоке finally в комментарии A, нужно ли мне все еще получать блокировку чтения, как это делает код в комментариях BиC? Или код может воспользоваться volatile?


person muzi_chelsea    schedule 20.01.2018    source источник


Ответы (1)


Нет, volatile не нужен независимо от того, понизили ли вы версию или нет (блокировка уже гарантирует потокобезопасный доступ к data). Это также не поможет с атомарностью, что и делает шаблон захвата-чтения-затем-записи-блокировки (и в чем был смысл вопроса).

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

Вам не нужно переходить на блокировку чтения, но если вы этого не сделаете, это сделает ваш код менее эффективным: если use(data) занимает 2 секунды (долгое время), то без понижения блокировки вы блокируете все остальные читатели для 2 секунды каждый раз, когда вы обновляете кеш.

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

В приведенном примере кода невозможно определить, действительно ли это имеет значение, поскольку информации недостаточно, но это создаст возможное дополнительное состояние для метода, и это не преимущество:

  • Один или несколько потоков находятся в use(data) с блокировками чтения
  • Один поток обновляет кеш, имея блокировку записи
  • Один поток находится в use(data) без блокировки, а другой поток обновляет кеш с блокировкой записи.
person Kayaman    schedule 20.01.2018
comment
О, мой вопрос не ясен. Я имею в виду, что если я использую volatile для data, мне все еще нужно получить блокировку чтения перед снятием блокировки записи. Я не знаю, может ли видимость, которую обеспечивает volatile, защитить data или нет. - person muzi_chelsea; 21.01.2018
comment
@muzi_chelsea Вам нужны блокировки, потому что здесь есть две переменные, которые должны синхронизироваться и читаться и записываться как одна атомарная операция. Если бы был только один, ты бы не стал. - person user207421; 21.01.2018