Использование 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;
}