Using putIfAbsent like a short circuit operator
Is it possible to use putIfAbsent or any of its equivalents like a short circuit operator.
myConcurrentMap.putIfAbsent(key,calculatedValue)
I want that if there is开发者_StackOverflow中文版 already a calculatedValue it shouldnt be calculated again. by default putIfAbsent would still do the calculation every time even though it will not actually store the value again.
Java doesn't allow any form of short-circuiting save the built-in cases, sadly - all method calls result in the arguments being fully evaluated before control passes to the method. Thus you couldn't do this with "normal" syntax; you'd need to manually wrap up the calculation inside a Callable
or similar, and then explicitly invoke it.
In this case I find it difficult to see how it could work anyway, though. putIfAbsent
works on the basis of being an atomic, non-blocking operation. If it were to do what you want, the sequence of events would roughly be:
- Check if
key
exists in the map (this example assumes it doesn't) - Evaluate
calculatedValue
(probably expensive, given the context of the question) - Put result in map
It would be impossible for this to be non-blocking if the value didn't already exist at step two - two different threads calling this method at the same time could only perform correctly if blocking happened. At this point you may as well just use synchronized
blocks with the flexibility of implementation that that entails; you can definitely implement what you're after with some simple locking, something like the following:
private final Map<K, V> map = ...;
public void myAdd(K key, Callable<V> valueComputation) {
synchronized(map) {
if (!map.containsKey(key)) {
map.put(key, valueComputation.call());
}
}
}
You can put Future<V>
objects into the map. Using putIfAbsent
, only one object will be there, and computation of final value will be performed by calling Future.get()
(e.g. by FutureTask
+ Callable
classes). Check out Java Concurrency in Practice for discussion about using this technique. (Example code is also in this question here on SO.
This way, your value is computed only once, and all threads get same value. Access to map isn't blocked, although access to value (through Future.get()
) will block until this value is computed by one of the threads.
You could consider to use a Guava ComputingMap
ConcurrentMap<Key, Value> myConcurrentMap = new MapMaker()
.makeComputingMap(
new Function<Key, Value>() {
public Value apply(Key key) {
Value calculatedValue = calculateValue(key);
return calculatedValue;
}
});
精彩评论