Thread-safe cache of one object in java Thread-safe cache of one object in java java java

Thread-safe cache of one object in java


google collections actually supplies just the thing for just this sort of thing: Supplier

Your code would be something like:

private Supplier<List<String>> supplier = new Supplier<List<String>>(){    public List<String> get(){        return loadCountryList();    }};// volatile reference so that changes are published correctly see invalidate()private volatile Supplier<List<String>> memorized = Suppliers.memoize(supplier);public List<String> list(){    return memorized.get();}public void invalidate(){    memorized = Suppliers.memoize(supplier);}


Thanks you all guys, especially to user "gid" who gave the idea.

My target was to optimize the performance for the get() operation considering the invalidate() operation will be called very rare.

I wrote a testing class that starts 16 threads, each calling get()-Operation one million times. With this class I profiled some implementation on my 2-core maschine.

Testing results

Implementation              Timeno synchronisation          0,6 secnormal synchronisation      7,5 secwith MapMaker               26,3 secwith Suppliers.memoize      8,2 secwith optimized memoize      1,5 sec

1) "No synchronisation" is not thread-safe, but gives us the best performance that we can compare to.

@Overridepublic List<String> list() {    if (cache == null) {        cache = loadCountryList();    }    return cache;}@Overridepublic void invalidateCache() {    cache = null;}

2) "Normal synchronisation" - pretty good performace, standard no-brainer implementation

@Overridepublic synchronized List<String> list() {    if (cache == null) {        cache = loadCountryList();    }    return cache;}@Overridepublic synchronized void invalidateCache() {    cache = null;}

3) "with MapMaker" - very poor performance.

See my question at the top for the code.

4) "with Suppliers.memoize" - good performance. But as the performance the same "Normal synchronisation" we need to optimize it or just use the "Normal synchronisation".

See the answer of the user "gid" for code.

5) "with optimized memoize" - the performnce comparable to "no sync"-implementation, but thread-safe one. This is the one we need.

The cache-class itself:(The Supplier interfaces used here is from Google Collections Library and it has just one method get(). see http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/base/Supplier.html)

public class LazyCache<T> implements Supplier<T> {    private final Supplier<T> supplier;    private volatile Supplier<T> cache;    public LazyCache(Supplier<T> supplier) {        this.supplier = supplier;        reset();    }    private void reset() {        cache = new MemoizingSupplier<T>(supplier);    }    @Override    public T get() {        return cache.get();    }    public void invalidate() {        reset();    }    private static class MemoizingSupplier<T> implements Supplier<T> {        final Supplier<T> delegate;        volatile T value;        MemoizingSupplier(Supplier<T> delegate) {            this.delegate = delegate;        }        @Override        public T get() {            if (value == null) {                synchronized (this) {                    if (value == null) {                        value = delegate.get();                    }                }            }            return value;        }    }}

Example use:

public class BetterMemoizeCountryList implements ICountryList {    LazyCache<List<String>> cache = new LazyCache<List<String>>(new Supplier<List<String>>(){        @Override        public List<String> get() {            return loadCountryList();        }    });    @Override    public List<String> list(){        return cache.get();    }    @Override    public void invalidateCache(){        cache.invalidate();    }    private List<String> loadCountryList() {        // this should normally load a full list from the database,        // but just for this instance we mock it with:        return Arrays.asList("Germany", "Russia", "China");    }}


Whenever I need to cache something, I like to use the Proxy pattern.Doing it with this pattern offers separation of concerns. Your originalobject can be concerned with lazy loading. Your proxy (or guardian) objectcan be responsible for validation of the cache.

In detail:

  • Define an object CountryList class which is thread-safe, preferably using synchronization blocks or other semaphore locks.
  • Extract this class's interface into a CountryQueryable interface.
  • Define another object, CountryListProxy, that implements the CountryQueryable.
  • Only allow the CountryListProxy to be instantiated, and only allow it to be referencedthrough its interface.

From here, you can insert your cache invalidation strategy into the proxy object. Save the time of the last load, and upon the next request to see the data, compare the current time to the cache time. Define a tolerance level, where, if too much time has passed, the data is reloaded.

As far as Lazy Load, refer here.

Now for some good down-home sample code:

public interface CountryQueryable {    public void operationA();    public String operationB();}public class CountryList implements CountryQueryable {    private boolean loaded;    public CountryList() {        loaded = false;    }    //This particular operation might be able to function without    //the extra loading.    @Override    public void operationA() {        //Do whatever.    }    //This operation may need to load the extra stuff.    @Override    public String operationB() {        if (!loaded) {            load();            loaded = true;        }        //Do whatever.        return whatever;    }    private void load() {        //Do the loading of the Lazy load here.    }}public class CountryListProxy implements CountryQueryable {    //In accordance with the Proxy pattern, we hide the target    //instance inside of our Proxy instance.    private CountryQueryable actualList;    //Keep track of the lazy time we cached.    private long lastCached;    //Define a tolerance time, 2000 milliseconds, before refreshing    //the cache.    private static final long TOLERANCE = 2000L;    public CountryListProxy() {            //You might even retrieve this object from a Registry.        actualList = new CountryList();        //Initialize it to something stupid.        lastCached = Long.MIN_VALUE;    }    @Override    public synchronized void operationA() {        if ((System.getCurrentTimeMillis() - lastCached) > TOLERANCE) {            //Refresh the cache.                    lastCached = System.getCurrentTimeMillis();        } else {            //Cache is okay.        }    }    @Override    public synchronized String operationB() {        if ((System.getCurrentTimeMillis() - lastCached) > TOLERANCE) {            //Refresh the cache.                    lastCached = System.getCurrentTimeMillis();        } else {            //Cache is okay.        }        return whatever;    }}public class Client {    public static void main(String[] args) {        CountryQueryable queryable = new CountryListProxy();        //Do your thing.    }}