Java ReentrantReadWriteLocks - как безопасно получить блокировку записи? - PullRequest
61 голосов
/ 21 января 2009

Я сейчас использую в своем коде ReentrantReadWriteLock для синхронизации доступа по древовидной структуре. Эта структура является большой и может быть прочитана многими потоками одновременно с редкими изменениями ее небольших частей, поэтому она, похоже, хорошо подходит для идиомы чтения-записи. Я понимаю, что с этим конкретным классом нельзя повысить блокировку чтения до блокировки записи, поэтому согласно Javadocs необходимо снять блокировку чтения до получения блокировки записи. Ранее я успешно использовал этот шаблон в нерецензируемых контекстах.

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

lock.getReadLock().unlock();
lock.getWriteLock().lock()

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

РЕДАКТИРОВАТЬ , чтобы прояснить это, поскольку я не думаю, что сначала я объяснил это слишком хорошо - я знаю, что в этом классе нет встроенного повышения блокировки, и что я должен просто выпустить блокировка чтения и получение блокировки записи. Моя проблема заключается в том, что независимо от того, что делают другие потоки, вызов getReadLock().unlock() может на самом деле не освободить этот поток, удерживающий блокировку, если он повторно ее получил, и в этом случае вызов getWriteLock().lock() будет блокировать навсегда, поскольку этот поток все еще удерживает блокировку чтения и таким образом блокирует себя.

Например, этот фрагмент кода никогда не достигнет оператора println, даже если он выполняется однопоточным без доступа других потоков к блокировке:

final ReadWriteLock lock = new ReentrantReadWriteLock();
lock.getReadLock().lock();

// In real code we would go call other methods that end up calling back and
// thus locking again
lock.getReadLock().lock();

// Now we do some stuff and realise we need to write so try to escalate the
// lock as per the Javadocs and the above description
lock.getReadLock().unlock(); // Does not actually release the lock
lock.getWriteLock().lock();  // Blocks as some thread (this one!) holds read lock

System.out.println("Will never get here");

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

Поскольку эта реализация ReadWriteLock была специально разработана для повторного входа, наверняка есть какой-то разумный способ повысить блокировку чтения до блокировки записи, когда блокировки могут быть получены повторно? Это критическая часть, которая означает, что наивный подход не работает.

Ответы [ 12 ]

0 голосов
/ 02 июля 2014

Найдено в документации для ReentrantReadWriteLock . В нем четко сказано, что потоки читателей никогда не преуспеют при попытке получить блокировку записи. То, что вы пытаетесь достичь, просто не поддерживается. Вы должны снять блокировку чтения до получения блокировки записи. Понижение еще возможно.

Реентерабельность

Эта блокировка позволяет и читателям, и писателям заново читать или писать блокировки в стиле {@link ReentrantLock}. Не реентерабельные читатели не допускаются, пока все блокировки записи, удерживаемые потоком записи, не будут иметь был освобожден.

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

Пример использования из вышеуказанного источника:

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

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // 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();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }

     use(data);
     rwl.readLock().unlock();
   }
 }
0 голосов
/ 21 января 2009

Используйте флаг "честно" на ReentrantReadWriteLock. «честный» означает, что запросы на блокировку обслуживаются в порядке поступления. Вы можете столкнуться с падением производительности, поскольку при выдаче запроса «запись» все последующие запросы «чтения» будут заблокированы, даже если они могли быть обработаны, пока существующие блокировки чтения все еще заблокированы.

...