Java Read Write Locks на ресурсосберегающей памяти - PullRequest
0 голосов
/ 04 сентября 2018

В памяти находится большая коллекция объектов типа R . Для изменения объекта требуется блокировка записи, а для чтения - блокировка чтения. Я мог бы хранить ReadWriteLock как закрытый член класса R , однако я хочу сохранить память. В любое время только небольшой процент объектов изменяется или читается. Существуют различные способы решить не сохранять блокировку чтения-записи для конкретного ресурса (например, если он не читался или не записывался в течение некоторого времени, t ). Для целей этого вопроса предположим, что периодически можно определить, что блокировка для ресурса может быть удалена. Однако имейте в виду, что пока блокировка ресурса удаляется в потоке, один или несколько других потоков могут попытаться изменить или прочитать ресурс. Все это происходит в многопоточной среде. Как бы вы реализовали это с наименьшим количеством блокировок?

Например, один из способов сделать это - сохранить блокировки чтения-записи в параллельной карте:

Map<R,ReadWriteLock> map = new ConcurrentHashMap<>();

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

Вы можете подумать, что можно использовать комбинацию computeifabsent и remove . Однако это не работает. Например:

//--Thread1 write lock--
ReadWriteLock rwl = map.computeIfAbsent(r, r -> new ReadWriteLock()); // 1
rwl.writeLock.lock();                                        // 4
//Modify r here

//--Thread2: Removing entry--
map.remove(r);                                               // 2

//Thread3: write lock
ReadWriteLock rwl = map.computeIfAbsent(r, r-> new ReadWriteLock()); // 3
rwl.writeLock.lock();                                        // 5
//Modify r here.

Проблема заключается в том, что объект блокировки потоком 1 не будет совпадать с блокировкой, полученной потоком 3, и неправильно разрешать две записи одновременно. Цифры справа показывают порядок исполнения.

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

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

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

Вы можете использовать методы compute и computeIfPresent в своих интересах. Важно сделать добавление / блокировку / удаление внутри потребителей, чтобы сделать это атомарно.

Примечание: вы использовали putIfAbsent в вашем примере, но это возвращает ранее присвоенное значение, а не недавно назначенное значение.

public static class Locks<R>
{
    private ConcurrentHashMap<R, ReentrantReadWriteLock> locks = new ConcurrentHashMap<>();

    public void lock(R r, Function<ReentrantReadWriteLock, Lock> whichLock)
    {
        locks.compute(r, (key, lock) -> {
            ReentrantReadWriteLock actualLock = lock == null ? new ReentrantReadWriteLock() : lock;
            whichLock.apply(actualLock).lock();
            return actualLock;
        });
    }

    public void unlock(R r, Function<ReentrantReadWriteLock, Lock> whichLock)
    {
        locks.computeIfPresent(r, (key, lock) -> {
            whichLock.apply(lock).unlock();
            return lock; // you could return null here if lock is unlocked (see cleanUp) to remove it immediately
        });
    }

    public void cleanUp()
    {
        for (R r : new ArrayList<>(locks.keySet()))
        {
            locks.computeIfPresent(r, (key, lock) -> locks.get(r).isWriteLocked()
                                                     || locks.get(r).getReadLockCount() != 0 ? lock : null);
        }
    }
}

Обратите внимание, как я использую

  • compute в lock для создания новых замков и их немедленной блокировки
  • computeIfPresent в unlock, чтобы проверить, есть ли блокировка вообще
  • computeIfPresent в cleanUp, чтобы проверить, нужна ли блокировка без блокировки другого потока, блокировка записи, пока я проверяю счетчик блокировки чтения

Прямо сейчас, unlock довольно бесполезно (за исключением нулевых проверок, что является лишь мерой предосторожности). Возвращение null в unlock может привести к очистке ненужных блокировок и сделает cleanUp устаревшим, но может увеличить потребность в создании новых блокировок. Это зависит от того, как часто используются блокировки.

Конечно, вы можете добавить удобные методы для чтения / записи, вместо того, чтобы предоставлять метод получения whichLock.

0 голосов
/ 04 сентября 2018

вопрос заключается в том, как сохранить блокировки чтения-записи для коллекции ресурсов без необходимости сохранять блокировку чтения-записи для каждого объекта в коллекции и минимизировать конфликт блокировки

Рассматривали ли вы использование полосатых замков? (например, https://google.github.io/guava/releases/19.0/api/docs/com/google/common/util/concurrent/Striped.html)

По сути, это набор из N блокировок для данных M, где N

...