查看接口putIfAbsent
中默认方法Map
的实现,
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
我想知道为什么要这么做
v = put(key, value);
在那里完成了,而不是简单地丢弃返回的值?这种分配似乎是不必要的,因为v
已经是null
了,在这种情况下,put
方法根据其合同总是会返回。
答案 0 :(得分:5)
实现之所以这样,是因为javadoc声明它应该是:
实施要求:
此地图的默认实现等效于
V v = map.get(key); if (v == null) v = map.put(key, value); return v;
但是为什么要这样指定呢?可能在不能被解决的种族条件下获得合理定义的行为 1 。
首先,这是javadoc所说的话。
默认实现不保证此方法的同步性或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
那么上述实现的表现如何?
如果不存在与另一个线程竞争该映射条目的竞争,它将返回null
或前一个值(不更新映射)。
如果有比赛,并且在get
调用之后另一个线程立即更新条目 ,那么map.put
调用将不会返回null
。相反,它实际上将返回另一个线程插入的值。然后该值将返回给调用方。
请注意,这与putIfAbsent
方法的主要描述不完全一致。但这是“没有保证”声明的借口。而且,这很有道理。
这有用吗?好吧……如果实际的Map
方法是线程安全的,可能是的。因为,如果调用代码想要查看,这将使其能够检测到发生了竞争,并且(如果在整个应用程序设计的上下文中有道理)可以尝试对此进行处理。
1-无法在不了解实现类的行为的默认方法中解决竞争条件。可以在实现类本身中解决它们……例如,通过重写方法以使其具有原子属性。并且Map
javadocs中的方法规范清楚地标记了这种可能性。
但是最重要的是,那里存在所谓的冗余分配,因为规范明确指出应该存在。
答案 1 :(得分:3)
它必须始终返回旧值。
如果映射被多个线程访问,则put
返回的值可能与get
返回的值不同,因此分配给v
可以确保更好的行为。
当然,该代码不适用于处理多线程访问,因此任何有效的线程安全实现都将覆盖该方法以使其更好地执行此操作,但这是通用默认实现可以达到的最好效果。