IsaacLevon
IsaacLevon

Reputation: 2570

How to update Cache from multiple threads

I have a Runnable which has a cache (of type Cache) and we assume that it offers thread safe operations. This Runnable object is used by multiple threads.

Our threads get objects from outer source and then

  1. check if the object key exists in the cache
  2. If not, then put
  3. If it's already in the cache then update

I'm looking for the right scheme (i.e. minimal synchronized code) to work with the cache, reliably.

I came up with the following scheme:

    MyObject current = cache.getIfPresent(givenKey);
    if (current == null) {
        MyObject prev = cache.asMap().putIfAbsent(givenKey, givenObj);
        if (prev == null) {
            // successful put in cache
            return givenObj;
        }
    }

    // current != null or another thread update
    synchronized (current) {
        return update(current, givenObj); // in place change of current
    }

The key ideas behind my scheme + "proof" of reliability:

  1. If threads work on different keys then no need to block
  2. If current is null, then since the cache is thread-safe, exactly one thread will be able to put the object in the cache whilst the others will see prev != null
  3. The other threads must update serially. Notice I'm syncing on current, the object to be updated.

Questions

  1. Is my scheme reliable?
  2. Can be optimised?
  3. In some cases, volatile must be used to make the memory synchronization reliable. Do I need it here?

Thanks!

Upvotes: 2

Views: 5394

Answers (2)

HPCS
HPCS

Reputation: 1454

1) no your schema is not reliable You should not call

cache.asMap().putIfAbsent(givenKey, givenObj);

by guava documentation method cache.get(K key, Callable loader) is preferable than to use asMap methods.

2) yes it can be optimised You should call instead this method:

cache.get(K key, Callable<? extends V> loader)

This method will return the value if already in cache, or it will add the value from the loader into the cache if the value is not in the cache and returns it.

so for example:

 MyObject objInCache =  cache.get(givenKey, ()->givenObj)

if(!objInCache.equals(givenobj)){
 //obje was in the cache,
//update object
}

3)you don't need volatile if the cache is thread safe

Upvotes: 3

Ben Manes
Ben Manes

Reputation: 9601

  1. check if the object key exists in the cache
  2. If not, then put
  3. If it's already in the cache then update

This can be accomplished using the Map view's compute method.

cache.asMap().compute(givenKey, (key, oldValue) -> {
  return (oldValue == null)
      ? create(key)
      : update(current, oldValue);
});

This is thread-safe and should allow for concurrent computations of different keys. If the returned new value is null then the mapping is removed, else it is established / updated.

Unfortunately this is not optimized in Guava, since it was scaffolded on when adding Java 8 compatibility for its additions. You should prefer 27.0.1 or newer as there were some nasty bugs.

Caffeine is a Java 8 rewrite that is designed for this feature. It builds upon lessons learned from Guava (similar interfaces, but revisits some design choices), modern algorithm research, and ecosystem improvements. Both are excellent, but you may find Caffeine is better suited for more advanced scenarios.

Upvotes: 0

Related Questions