6

I am caching an object, which is created by a thread, into a map. The creation of the object is expensive, so I don't want multiple threads running to create the object because the put() hasn't returned. Once a thread tries to create an object for that key, other threads shouldn't try to create the object, even if put is not yet complete. Will using computeIfAbsent() work to acquire a 'lock' on that particular key? If not, is there another way to achieve this?

dt94
  • 87
  • 1
  • 3
  • 9
  • 1
    For a proper, efficient and lightweight Java 8 caching library, you could have a look at [Caffeine](https://github.com/ben-manes/caffeine/wiki/Population). It abstracts away the stuff you're worrying about here. For Java < 8, [Guava caches](https://github.com/google/guava/wiki/CachesExplained) provide pretty much the same functionality. – Mick Mnemonic Aug 06 '18 at 22:49
  • 1
    I ended up using a Guava LoadingCache, like you said it does everything I was trying to achieve. – dt94 Aug 13 '18 at 22:55

2 Answers2

11

> Will using computeIfAbsent() work to acquire a 'lock' on that particular key?

Yes; per the Javadoc for ConcurrentHashMap.computeIfAbsent(...):

The entire method invocation is performed atomically, so the function is applied at most once per key.

That's really the whole point of the method.

However, to be clear, the lock is not completely specific to that one key; rather, ConcurrentHashMap typically works by splitting the map into multiple segments, and having one lock per segment. This allows a great deal of concurrency, and is usually the most efficient approach; but you should be aware that it means that some threads might block on your object creation even if they're not actually touching the same key.

If this is a problem for you, then another approach is to use something like ConcurrentHashMap<K, AtomicReference<V>> to decouple adding the map entry from populating the map entry. (AtomicReference<V> doesn't have a computeIfAbsent method, but at that point you can just use normal double-checked locking with a combination of get() and synchronized.)

ruakh
  • 175,680
  • 26
  • 273
  • 307
0

Took some research, but we were probably after is the Java ConcurrentHashMap equivalent of .NET's .TryAdd method. Which is Java world is:

putIfAbsent

public V putIfAbsent(K key, V value);

If the specified key is not already associated with a value, associate it with the given value. This is equivalent to:

if (!map.containsKey(key))
   return map.put(key, value);
else
   return map.get(key);

except that the action is performed atomically.

I knew an atomic add operation had to exist; just was not easy to find. (which is odd because it's like the very first thing anyone would ever need to call).

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219