是否可以使用 putIfAbsent 或其任何等效项,例如短路运算符。
myConcurrentMap.putIfAbsent(key,calculatedValue)
我希望如果已经有计算值,则不应再次计算。默认情况下,putIfAbsent 仍然会每次都进行计算,即使它实际上不会再次存储该值。
是否可以使用 putIfAbsent 或其任何等效项,例如短路运算符。
myConcurrentMap.putIfAbsent(key,calculatedValue)
我希望如果已经有计算值,则不应再次计算。默认情况下,putIfAbsent 仍然会每次都进行计算,即使它实际上不会再次存储该值。
Java不允许任何形式的短路保存内置案例,遗憾的是 - 所有方法调用都会导致在控制传递给方法之前对参数进行完全评估。因此,您无法使用“正常”语法来做到这一点;您需要手动将计算包含在 aCallable
或类似文件中,然后显式调用它。
不过,在这种情况下,我发现很难看出它是如何工作的。 putIfAbsent
基于原子的、非阻塞操作的基础上工作。如果要按照您的意愿行事,则事件的顺序大致是:
key
地图中是否存在(此示例假设不存在)calculatedValue
(考虑到问题的背景,可能很昂贵)如果在第二步中该值不存在,那么它就不可能是非阻塞的 - 同时调用此方法的两个不同线程只有在发生阻塞时才能正确执行。此时,您不妨只使用synchronized
具有实现灵活性的块;您绝对可以通过一些简单的锁定来实现您所追求的,如下所示:
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());
}
}
}
您可以将Future<V>
对象放入地图中。使用putIfAbsent
,只有一个对象存在,最终值的计算将通过调用Future.get()
(例如通过FutureTask
+Callable
类)来执行。查看Java Concurrency in Practice以了解有关使用此技术的讨论。(示例代码也在这个问题中。
这样,您的值只计算一次,并且所有线程都获得相同的值。对 map 的访问不会被阻止,尽管对 value 的访问(通过Future.get()
)将被阻止,直到该值由其中一个线程计算。
您可以考虑使用Guava ComputingMap
ConcurrentMap<Key, Value> myConcurrentMap = new MapMaker()
.makeComputingMap(
new Function<Key, Value>() {
public Value apply(Key key) {
Value calculatedValue = calculateValue(key);
return calculatedValue;
}
});