ReentrantReadWriteLock — много читателей одновременно, один писатель за раз?

Я новичок в многопоточных средах и пытаюсь найти лучшее решение для следующей ситуации:

Я читаю данные из базы данных один раз в день утром и сохраняю данные в HashMap в объекте Singleton. У меня есть метод установки, который вызывается только тогда, когда происходит внутридневное изменение БД (которое будет происходить 0-2 раза в день).

У меня также есть геттер, который возвращает элемент на карте, и этот метод вызывается сотни раз в день.

Меня беспокоит случай, когда геттер вызывается, когда я очищаю и воссоздаю HashMap, таким образом пытаясь найти элемент в пустом/искаженном списке. Если я сделаю эти методы синхронизированными, это предотвратит одновременный доступ двух читателей к геттеру, что может стать узким местом в производительности. Я не хочу слишком сильно снижать производительность, поскольку записи происходят очень редко. Если я использую ReentrantReadWriteLock, будет ли это вызывать очередь для любого, кто вызывает геттер, пока блокировка записи не будет снята? Позволяет ли он нескольким читателям одновременно обращаться к геттеру? Будет ли он применять только одного писателя одновременно?

Кодирование это просто вопрос...

private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
private final Lock write = readWriteLock.writeLock();

public HashMap getter(String a) {
    read.lock();
    try {
        return myStuff_.get(a);            
    } finally {
        read.unlock();
    }
}

public void setter() 
{
    write.lock();
    try {
        myStuff_ = // my logic
     } finally {
          write.unlock();
    }
}

person Sarah    schedule 12.05.2011    source источник


Ответы (3)


Другой способ добиться этого (без использования блокировок) — шаблон копирования при записи. Это хорошо работает, когда вы не пишете часто. Идея состоит в том, чтобы скопировать и заменить само поле. Это может выглядеть следующим образом:

private volatile Map<String,HashMap> myStuff_ = new HashMap<String,HashMap>();

public HashMap getter(String a) {
    return myStuff_.get(a);
}

public synchronized void setter() {
    // create a copy from the original
    Map<String,HashMap> copy = new HashMap<String,HashMap>(myStuff_);
    // populate the copy
    // replace copy with the original
    myStuff_ = copy;
}

При этом читатели полностью параллельны, и единственный штраф, который они платят, — это изменчивое чтение на myStuff_ (что очень мало). Писатели синхронизированы для обеспечения взаимного исключения.

person sjlee    schedule 13.05.2011
comment
Отличный ответ. Обратите внимание: если вы знаете, что есть только один модуль записи, вам не нужно синхронизировать setter(). Кроме того, если доступ к старой карте не является проблемой, вам не понадобится volatile, так как доступ к ссылкам всегда атомарный (но вы можете использовать старую карту в течение длительного времени). - person ninjalj; 13.05.2011
comment
Если есть только один писатель, синхронизацию можно пропустить. Однако вам все еще нужна volatile, как это происходит раньше. Без volatile вы бы страдали от проблем с переупорядочением. - person sjlee; 14.05.2011
comment
вот почему я сказал, что если доступ к старой карте не является проблемой, и вы можете использовать старую карту в течение длительного времени :) - person ninjalj; 14.05.2011
comment
Наличие устаревшей ссылки является одним из последствий, но изменение порядка может вызвать более серьезные проблемы. Другой поток может увидеть новую ссылку (myStuff_) до завершения операции установки из-за переупорядочения. - person sjlee; 14.05.2011
comment
ах, да, я пропустил это. Так что volatile действительно нужен. - person ninjalj; 14.05.2011
comment
Никакие побочные эффекты атомарного действия не видны до тех пор, пока действие не будет завершено. Чтение и запись являются атомарными для всех переменных, объявленных как volatile. = копирование, геттер (строка a) будет блокироваться, пока myStuff_ = копирование не завершится - person Amitābha; 25.08.2013

Да, если блокировка записи удерживается потоком, тогда другие потоки, обращающиеся к методу получения, будут заблокированы, поскольку они не могут получить блокировку чтения. Так что тебе здесь хорошо. Для получения более подробной информации ознакомьтесь с JavaDoc ReentrantReadWriteLock — http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html

person kuriouscoder    schedule 12.05.2011

Вы запускаете эту вещь в начале дня... вы обновляете ее 0-2 раза в день и читаете ее по 100 раз в день. Предполагая, что чтение займет, скажем, 1 полную секунду (долгое время) за 8-часовой день (28800 секунд), у вас все еще будет очень низкая нагрузка на чтение. Глядя на документы для ReentrantReadWriteLock, вы можете «подправить» режим, чтобы он был справедливым, что означает, что поток, который ждал дольше всего, получит блокировку. Поэтому, если вы сделаете это справедливым, я не думаю, что ваши потоки записи будут голодать.

использованная литература

ReentrantReadWriteLock

person Andrew    schedule 12.05.2011
comment
Выполняет ли включенный мной код сценарий с несколькими считывателями, который я описал? Я только хочу, чтобы читатели не читали, если в данный момент происходит запись. В день происходят сотни прочтений, но в любой момент времени они могут прийти пачками и сделать 20-30 обращений к моему геттеру. - person Sarah; 12.05.2011