Path: csiph.com!newsfeed.hal-mli.net!feeder3.hal-mli.net!newsfeed.hal-mli.net!feeder1.hal-mli.net!news.mixmin.net!news.musoftware.de!wum.musoftware.de!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail From: Robert Klemme Newsgroups: comp.lang.java.programmer Subject: Re: multithreaded cache? Date: Thu, 17 May 2012 12:06:34 +0200 Lines: 152 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit X-Trace: individual.net YI8r3IEzCB8epHDNBgxF8QiwrOD5U9M6XCz7Gq4ravUiSEZGFRFnGRfFb8MxKEMGE= Cancel-Lock: sha1:38AmX1td9Hkzk4D5HYBa8p/GbD4= User-Agent: Mozilla/5.0 (X11; Linux i686; rv:12.0) Gecko/20120430 Thunderbird/12.0.1 In-Reply-To: Xref: csiph.com comp.lang.java.programmer:14572 On 05/17/2012 11:54 AM, Robert Klemme wrote: > On 05/15/2012 11:14 AM, bugbear wrote: >> However, if the underlying function is slow >> and/or resource hungry (consider cacheing >> a ray traced image!) many threads can >> end up calling the real function (second >> and subsequent threads to the first get a miss >> during the first threads call to the underlying function). >> >> "obviously..." what I want is for only >> the thread that FIRST has a cache miss >> calls the underlying function, whilst other >> threads (for the same key) wait. > > I provide a variant of Silvio's, Eric's and Daniel's solution which > should yield higher throughput because it works without read write > locking. You can find it as gist in case the code is garbled in the > newsgroup posting: > https://gist.github.com/2717818 There was one important detail missing. This is the corrected code (gist is updated as well): package clj; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * The cache works with as few locking as possible. Lookup is done in two steps * on cache miss: *
    *
  1. On a cache miss a retriever is inserted into the cache which will obtain * the value synchronized from a {@link Calculator}.
  2. *
  3. Once calculation has finished a simple lock free reference to the value * replaces the retriever in the cache and the value is returned.
  4. *
* * @author robert klemme * * @param * key type * @param * value type */ public final class LazyCache { /** * Calculate values from given keys. * * @param * key type * @param * value type */ public interface Calculator { V get(K key); } /** * Obtain a value. * * @param * value type. */ private interface Reference { V get(); } /** * Stupid simple reference which only hands out a fixed value all the time * without synchronization. * * @param * value type. */ private static final class Ref implements Reference { private final V val; public Ref(V val) { this.val = val; } @Override public V get() { return val; } } /** Mapping from keys to objects which yield values. */ private final ConcurrentMap> map = new ConcurrentHashMap>(); /** User provided. */ private final Calculator calc; /** * Create a cache. * * @param calc * user must provide a reasonable implementation, not * null. */ public LazyCache(final Calculator calc) { if (calc == null) throw new NullPointerException(); this.calc = calc; } /** * Get a value from the cache. The value might have to be calculated. * * @param key * lookup key. * @return value, might even be null depending on algorithm. */ public V get(final K key) { Reference ref = map.get(key); if (ref == null) { // miss ref = new Reference() { private V val; private boolean here = false; // superfluous but explicit @Override public synchronized V get() { if (!here) { val = calc.get(key); here = true; // next time lock free access: Reference x = map.put(key, new Ref(val)); assert x == this; } return val; } }; final Reference old = map.putIfAbsent(key, ref); if (old != null) ref = old; // someone else was faster } return ref.get(); } }