What is the best way to make this snippet thread-safe?
private static final Map<A, B> MAP = new HashMap<A, B>();
public static B putIfNeededAndGet(A key) {
B value = MAP.get(key);
if (value == null) {
value = buildB(...);
MAP.put(key, value);
}
return value;
}
private static B buildB(...) {
// business, can be quite long
}
Here are the few solutions I could think about:
ConcurrentHashMap
, but if I well understood, it just makes the atomic put
and get
operations thread-safe, i.e. it does not ensure the buildB()
method to be called only once for a given value.Collections.synchronizedMap(new HashMap<A, B>())
, but I would have the same issue as the first point.putIfNeededAndGet()
method synchronized
, but I can have really many threads accessing this method together, so it could be quite expensive.What other solutions may I have?
I know this is a quite common topic on the Web, but I didn't find a clear, full and working example yet.
Use ConcurrentHashMap and the lazy init pattern which you used
public static B putIfNeededAndGet(A key) {
B value = map.get(key);
if (value == null) {
value = buildB(...);
B oldValue = map.putIfAbsent(key, value);
if (oldValue != null) {
value = oldValue;
}
}
return value;
}
This might not be the answer you're looking for, but use the Guava CacheBuilder
, it already does all that and more:
private static final LoadingCache<A, B> CACHE = CacheBuilder.newBuilder()
.maximumSize(100) // if necessary
.build(
new CacheLoader<A, B>() {
public B load(A key) {
return buildB(key);
}
});
You can also easily add timed expiration and other features as well.
This cache will ensure that load()
(or in your case buildB
) will not be called concurrently with the same key
. If one thread is already building a B
, then any other caller will just wait for that thread.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With