1

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.
Michael Kohne
  • 11,888
  • 3
  • 47
  • 79
raffian
  • 31,267
  • 26
  • 103
  • 174
  • Usually, caches are used in concurrent environment, so mutual exclusion is not something tangential for them. There's little sense for this `Cache` to be used by single thread. So why you make `Cache` methods `public`? Imho, it's more clear to have `Cache` as interface, then have an implementation which is thread-safe by default. Then, have you looked at Guava `CacheBuilder`? – Victor Sorokin Jul 04 '14 at 19:22
  • @VictorSorokin We have `Cache` interface, did not show since it's not relevant to question. Why do you say this cache is used by single thread? We intend to use in concurrent environment, so read locks allow multiple threads to read concurrently, no? I got the blocking decorator idea from here: http://bit.ly/1j5lDvE – raffian Jul 04 '14 at 19:47
  • I mean, your `SimpleCache` is `public` (class, ctor and methods are `public`), so it's possible to directly instantiate and use it. And since it's not thread-safe, it can be used correctly only by single thread. In mybatis link you posted, I assume `delegate` class isn't public. – Victor Sorokin Jul 04 '14 at 19:52
  • 1
    Given that anyone can call the unprotected `get()` and `set()` methods of the `SimpleCache` class, the answer is a definite no. I have to admit though that I don't really understand why the most intrinsic feature of a cache (namely that it stores and retrieves information for multiple threads) requires a decorator in the first place. – biziclop Jul 04 '14 at 19:59
  • 1
    I have used this form of decorator pattern for caches before, and it is very powerful for mix and matching different cache features. Most libraries have flags for turning features on and off, and then lots of if checks which muddy the water. Creating a cache strategy through composition can be very nice to work with. But it may not be for every one. – Chris K Jul 04 '14 at 20:04
  • 1
    Composition is fine but it should be done in a way that prevents the "leakage" of unsynchronised instances. (A lot of other features may depend on this too, like implementing a separate upper size limit.) – biziclop Jul 04 '14 at 20:12
  • 1
    @biziclop Decorators allow behavioral concerns to be externalized and reused, for instance, we have `BlockingDecorator`, `ReadOnlyDecorator`, `ReadWriteDecorator`, etc., it makes the cache code much cleaner. I know that `get()/set()` on SimpleCache are wide open, in the actual implementation they are protected using closures, my bad, I should have mentioned that earlier. – raffian Jul 04 '14 at 20:14
  • In that case they are indeed safe :) – biziclop Jul 04 '14 at 20:15

2 Answers2

1
  • Is this code thread-safe?

Yes. Assuming that the instance of the decorated SimpleCache is not passed about.

  • 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.

No. Although it is also worth noting that as discussed in comments, BlockingCacheDecorator would usually implement a Cache interface.

Chris K
  • 11,622
  • 1
  • 36
  • 49
  • The Decorator does implement the interface, wanted to keep the example simple, I should have mentioned the assumptions, my bad! :-D – raffian Jul 04 '14 at 20:22
1

In its current form the code is trivially non-threadsafe, as there's nothing preventing a caller from calling methods of SimpleCache directly, or indeed pass the same SimpleCache instance to multiple decorators, causing even more mayhem.

If you promise never to do that, it is technically thread-safe, but we all know how much those promises are worth.

If the aim is to be able to use different implementations of underlying caches, I'd create a CacheFactory interface:

interface CacheFactory {
  Cache newCache();
}

A sample implementation of the factory:

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 );
  }
}

And finally your delegate class:

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
}

This way there's a much stronger guarantee that your Cache instances won't be inadvertently reused or accessed by an external agent. (That is, unless the factory is deliberately mis-implemented, but at least your intention not to reuse Cache instances is clear.)

Note: you can also use an anonymous inner class (or possibly a closure) to provide the factory implementation.

biziclop
  • 48,926
  • 12
  • 77
  • 104