We're building cache stores in our app backed by memory, file, and remote services. Want to avoid explicit synchronization to keep the stores simple while using decorators for behavioral concerns like blocking.
Here's a simple cache, this is just an example!
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()
returns a decorator providing synchronization for get()
and set()
, while isKeyStale()
and refreshKey()
allows the decorator to check if a key should be refreshed without knowing why or how. I got the idea for a synchronizing decorator from here.
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();
}
}
Questions:
- Assuming
SimpleCache
is only ever used via its decorator, is the code thread-safe? - Is it bad practice for
ReadWriteLock
to be declared outside the class being synchronized?SimpleCache.getDecorator()
ensures a 1-to-1 mapping between cache and decorator instances, so I'm assuming this is ok.