Использование ReentrantReadWriteLock и логического флага

У меня есть кэш, который загружается заранее с большим объемом данных (фоновым потоком) и не может использоваться до полного заполнения (он также будет перезагружаться время от времени и будет непригодным для использования во время этой загрузки). Я хочу, чтобы классы, которые используют его, чтобы проверить флаг isLoaded() до доступа. Я использую ReentrantReadWriteLock (я опускаю это в коде для простоты) для управления доступом следующим образом:

public class Cache {

   private volatile boolean loaded = false; //starts false

   private static String[] cache;

   private static Lock readLock;
   private static Lock writeLock;

   public Object get(Object key) {
       if (!readLock.tryLock()) throw IllegalStateException(...);
       try {
           ... do some work
       } finally {
           readLock.unlock();
       }
   }

   // called by background thread
   private void loadFull() {
      loaded = false;
      writeLock.lock()
      try {
          cache = new String[];
          ... fill cache
      } finally {
          writeLock.unlock();
          loaded = true;
      }
   }
....
}  

Теперь в моем другом классе у меня есть такой блок:

if (cache.isLoaded()) {
    try {
      Object x = cache.get(y);
    } catch (IllegalStateException iex) {
      // goto database for object
    }
} else {
    // goto database for object
}

Мне действительно нужно try/catch? Возможно ли когда-нибудь, чтобы флаг был установлен в ложь и попытка readLock () не удалась? Должен ли я вообще беспокоиться о флаге и просто поймать исключение (поскольку я в основном делаю тот же код, если исключение выдается, как если бы флаг был ложным). Я просто чувствую, что делаю что-то немного не так, но не могу понять, что это. Благодарю.

3 ответа

Решение

Мне действительно нужно попробовать / поймать? Возможно ли когда-нибудь, чтобы флаг был установлен в ложь и попытка readLock () не удалась?

Да, тебе это нужно. Между временем cache.isLoaded() а также cache.get() называются, писатель может прийти и получить блокировку записи - в этом случае cache.isLoaded() вернусь true, но cache.get() выбросит исключение.

Должен ли я вообще беспокоиться о флаге и просто поймать исключение (поскольку я в основном делаю тот же код, если исключение выдается, как если бы флаг был ложным).

Из кода, который вы показали, исключение выдается только в тех случаях, когда get не может получить блокировку чтения. Получение блокировки чтения завершается неудачно, только если в это время имеется одновременно записывающее устройство. isLoaded также возвращает false именно в этом сценарии. Поэтому достаточно полагаться на исключение. Кроме того, рассмотреть вопрос о создании специализированного CacheStaleException,

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

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

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

Предполагая, что вы действительно хотите перейти в базу данных, если блокировка чтения не доступна сразу, я бы сделал это:

   public Object get(Object key) {
       Object returnValue;
       if (readLock.tryLock()) {
           try {
               ... do some work
               returnValue = ...
           } finally {
               readLock.unlock();
           }
       } else {
           //go to database
           returnValue = ...
       }
       return returnValue;
   }
Другие вопросы по тегам