Мы создаем хранилища кешей в нашем приложении, поддерживаемые памятью, файлами и удаленными службами. Хотите избежать явной синхронизации, чтобы хранить магазины просто, используя декораторы для поведенческих проблем, таких как блокировка.
Вот простой кеш, это всего лишь пример!
import java.util.HashMap;
public class SimpleCache {
private HashMap<String,Object> store;
private final BlockingCacheDecorator decorator;
public SimpleCache(){
store = new HashMap<String,Object>();
decorator = new BlockingCacheDecorator(this);
}
//is NOT called directly, always uses decorator
public Object get(String key){
return store.get(key);
}
//is NOT called directly, always uses decorator
public void set(String key, Object value){
store.put(key, value);
}
//is NOT called directly, always uses decorator
public boolean isKeyStale(String key){
return !(store.containsKey(key));
}
//is NOT called directly, always uses decorator
public void refreshKey(String key){
store.put(key, new Object());
}
public BlockingCacheDecorator getDecorator(){
return decorator;
}
}
getDecorator()
возвращает декоратор, обеспечивающий синхронизацию для get()
и set()
, тогда как isKeyStale()
и refreshKey()
позволяет декоратору проверять, должен ли обновляться ключ, не зная, почему и как. У меня есть идея для синхронизации декоратора из здесь.
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class BlockingCacheDecorator {
private SimpleCache delegate;
private final ReentrantReadWriteLock lock;
public BlockingCacheDecorator(SimpleCache cache){
delegate = cache;
lock = new ReentrantReadWriteLock();
}
public Object get(String key){
validateKey(key);
lockForReading();
try{
return delegate.get(key);
}finally{ readUnlocked(); }
}
public void setKey(String key, Object value){
lockForWriting();
try{
delegate.set(key,value);
}finally{ writeUnlocked(); }
}
protected void validateKey(String key){
if(delegate.isKeyStale(key)){
try{
lockForWriting();
if(delegate.isKeyStale(key))
delegate.refreshKey(key);
}finally{ writeUnlocked(); }
}
}
protected void lockForReading(){
lock.readLock().lock();
}
protected void readUnlocked(){
lock.readLock().unlock();
}
protected void lockForWriting(){
lock.writeLock().lock();
}
protected void writeUnlocked(){
lock.writeLock().unlock();
}
}
SimpleCache
используется только через его декоратор, безопасен ли код?ReadWriteLock
вне синхронизируемого класса? SimpleCache.getDecorator()
обеспечивает сопоставление 1 к 1 между экземплярами кеша и декоратора, поэтому я предполагаю, что это нормально.Да. Предполагая, что экземпляр декорированного SimpleCache не передается.
Нет. Хотя стоит также отметить, что, как обсуждалось в комментариях, BlockingCacheDecorator обычно реализует интерфейс Cache.
В его нынешнем виде код тривиально не-потокобезопасен, поскольку ничего не мешает вызывающему SimpleCache
напрямую обращаться к методам SimpleCache
или передавать один и тот же экземпляр SimpleCache
нескольким декораторам, что приводит к еще большему хаосу.
Если вы обещаете никогда не делать этого, это технически потокобезопасно, но мы все знаем, насколько эти обещания стоят.
Если цель состоит в том, чтобы иметь возможность использовать различные реализации базовых кешей, я бы создал интерфейс CacheFactory
:
interface CacheFactory {
Cache newCache();
}
Пример внедрения завода:
class SimpleCacheFactory implements CacheFactory {
private final String cacheName; //example cache parameter
public SimpleCacheFactory( String cacheName ) {
this.cacheName = cacheName;
}
public Cache newCache() {
return new SimpleCache( cacheName );
}
}
И, наконец, ваш класс делегатов:
public class BlockingCacheDecorator {
private final Cache delegate;
private final ReentrantReadWriteLock lock;
public BlockingCacheDecorator(CacheFactory factory){
delegate = factory.newCache();
lock = new ReentrantReadWriteLock();
}
//rest of the code stays the same
}
Таким образом, гораздо более надежная гарантия того, что ваши экземпляры Cache
не будут повторно использоваться повторно или получить доступ к внешнему агенту. (То есть, если фабрика намеренно неправильно реализована, но по крайней мере ваше намерение не повторять использование экземпляров Cache
ясно).
Примечание. Вы также можете использовать анонимный внутренний класс (или, возможно, закрытие) для обеспечения заводской реализации.
Cache
одним потоком. Так почему же вы делаете методыCache
public
? Имхо, более понятно иметьCache
качестве интерфейса, а затем иметь реализацию, которая по умолчанию является поточно-ориентированной. Тогда вы смотрели на GuavaCacheBuilder
?Cache
, не показывал, так как он не имеет отношения к вопросу. Почему вы говорите, что этот кеш используется одним потоком? Мы намерены использовать его в параллельной среде, поэтому блокировки чтения позволяют нескольким потокам читать одновременно, не так ли? Я получил идею блокирующего декоратора отсюда: bit.ly/1j5lDvE