我想讨论我对并发映射的特定用法,以检查我的逻辑......
如果我使用ConcurrentHashMap
,我可以做熟悉的
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();
public V getExampleOne(K key) {
map.putIfAbsent(key, new Object());
return map.get(key);
}
但我意识到存在竞争条件,如果我从putIfAbsent
和get
之间的地图中删除该项,则上述方法会返回集合中存在的时间更长。这可能会或可能不会很好,但我们假设对于我的用例,它不行。
我真正喜欢的是让整个事物成为原子。所以,
public V getExampleTwo(K key) {
return map.putIfAbsent(key, new Object());
}
但随着这扩展到
if (!map.containsKey(key))
return map.put(key, value); [1]
return map.get(key);
对于第[1]行,第一次使用会返回null
(即map.put
将返回上一个值,第一次使用时为null
)。
我不能让它在此实例中返回null
这让我有类似的东西;
public V getExampleThree(K key) {
Object object = new Object();
V value = locks.putIfAbsent(key, object);
if (value == null)
return object;
return value;
}
所以,最后,我的问题;上面的例子在语义上有何不同? getExampleThree
确保getExampleTwo
之类的原子性,但是正确地避免空值返回吗? getExampleThree
还有其他问题吗?
我希望对这些选择进行一些讨论。我意识到我可以使用非ConcurrentHashMap
并在调用我的get
方法的客户端和从地图中删除的方法同步,但这似乎打败了ConcurrentHashMap的目的(非阻塞性质)。 这是我保持数据准确的唯一选择吗?
我想这是你选择ConcurrentHashMap的原因之一;它在您与之交互时的可见/最新/显示,但如果旧数据将成为一个问题,可能会产生进一步的影响......
答案 0 :(得分:5)
听起来您正在尝试为密钥创建全局锁定对象。
我不会删除一个可能几乎立即重新创建的条目,而是在您确定不再需要它时,我只会删除该条目。
否则,如果您将其用于锁定,则可以在同一个键的不同对象上使用两个线程锁定。
如果不行,你可以忙着循环它。
public V getExampleOne(K key) {
for(Object o = null, ret = null; (ret = map.get(key)) == null; )
map.putIfAbsent(key, o == null ? o = new Object() : o);
return ret;
}
一旦循环存在,它仍然可以被删除或替换,因此它实际上与。
基本相同public V getExampleThree(K key) {
Object o = new Object();
map.putIfAbsent(key, o);
Object ret = map.get(key);
return ret == null ? o : ret;
}
所以,最后,我的问题;上面的例子在语义上有何不同?。
差异只是显而易见的。
getExampleThree是否确保像getExampleTwo这样的原子性但是正确地避免了null返回?
是
getExampleThree还有其他问题吗?
只有当您认为下次通话可能不会给您一个不同的价值时(如果您认为可以在另一个主题中将其删除)
答案 1 :(得分:3)
这些方法有不同的语义:
但是,根据情况,当您使用返回值时,它可能不是实际对象。然后,您需要显式锁定。
答案 2 :(得分:0)
为什么不简单地使用第一个版本并同步方法?
public synchronized V getExampleOne(K key) {
map.putIfAbsent(key, new Object());
return map.get(key);
}
虽然它不能为您提供最大的并行性,但您也只有两个操作getExampleThree
,而且正确,对于阅读代码的其他人来说,可读性较差且不易理解。
答案 3 :(得分:0)
我认为你会发现诀窍是假设你将是非原子的并处理它。
我不太清楚你在寻找什么。让我知道如果这是切断的,我会修改。
也许您正在寻找类似的东西:
private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap();
/*
* Guaranteed to return the object replaced.
*
* Note that by the time this method returns another thread
* may have already replaced the object again.
*
* All we can guarantee here is that the return value WAS
* associated with the key in the map at the time the entry was
* replaced.
*
* A null return implies that either a null object was associated
* with the specified key or there was no entry in the map for
* the specified key wih 'null' as it's 'value'.
*/
public Object consistentReplace ( String key, Object newValue ) {
Object oldValue = map.get(key);
while ( !map.replace(key, oldValue, newValue) ) {
// Failed to replace because someone got in there before me.
oldValue = map.get(key);
}
return oldValue;
}