[concurrency-interest] memoizer cache

bhm bheem at sbcglobal.net
Fri Oct 21 10:39:52 EDT 2011


I'm trying to build a cache for configuration objects for my projects-
Requirements:
 1 only one instance per key should be created, either because its absent
   for a given key or its expired (in that case it will be recreated).
 2 most common path asking for a value for a key should not block or
synchronize.
 3 when a value doesnt exists for a key, one thread should create the instance
   of value and put in the cache, other threads asking for same key should wait
   till thats complete.
 4 if a value is expired, one thread should (re)create the instance of value
   and other threads asking for same key (when one thread is recreating it)
   should use expired instance of value and should not block.

My code is largely derived from Christian's reply
http://cs.oswego.edu/pipermail/concurrency-interest/2010-November/007469.html

Please comment or suggest on following, Thanks for you help.

    final ConcurrentMap<K, Object> store = new ConcurrentHashMap<K, Object>();

    V get(K key) {
        Object ret = store.get(key);

        if (ret == null) {
            CountDownLatch latch = new CountDownLatch(1);
            ret = store.putIfAbsent(key, latch);
            if (null == ret) {
                ret = new Entry<V>(factory.create(key));
                store.replace(key, ret);
                latch.countDown();
            }
        }

        if (ret instanceof CountDownLatch) {
            try {((CountDownLatch) ret).await();} // till new value is created
            catch (InterruptedException e) {throw new RuntimeException(e);}
            ret = store.get(key); // get new value
        } else {
            Entry<V> entry = (Entry<V>) ret;

            if (evictionpolicy != null && evictionpolicy.isExpired(entry.v)) {
                final AtomicInteger sync = entry.sync;

                if (sync.compareAndSet(0, 1)) { // otherwise retrun old value
                    try {
                        entry = (Entry<V>) store.get(key); // get again
                        if (evictionpolicy.isExpired(entry.v)) { // double-check
                            Entry<V> newval = new Entry<V>(factory.create(key));
                            store.replace(key, newval);
                            ret = newval;
                        }
                    }
                    catch (Throwable catchall) {
                      catchall.printStackTrace(System.err);//return old value
                    }
                    finally {sync.set(0);/*unlock*/}
                }
            }
        }

        return ((Entry<V>) ret).v;
    }

    class Entry<V> {
        final AtomicInteger sync = new AtomicInteger(0);
        final V v;
        Entry(V v) {this.v=v;}
    }


form other discussions about similar topics in the list, I understand there are
libraries to do that (MapMaker etc.), I wanted something specific, small, just
to slove the given problem and avoid generalization.
I couldnt get jcp's Memoizer to work when cache entries could expire. Its good
for lazy one time load.

Thanks again.


More information about the Concurrency-interest mailing list