Reputation: 2570
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
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:
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
current
, the object to be updated. Questions
volatile
must be used to make the memory synchronization reliable. Do I need it here?Thanks!
Upvotes: 2
Views: 5394
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
Reputation: 9601
- check if the object key exists in the cache
- If not, then put
- 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