简体   繁体   English

ConcurrentHashMap putIfAbsent:当跟随get()调用时的原子性

[英]ConcurrentHashMap putIfAbsent : atomicity when followed by a get() call

I wanted to discuss a specific use I have of a concurrent map to sense check my logic... 我想讨论一个特定的用途,我有一个并发映射来感知检查我的逻辑......

If I used ConcurrentHashMap , I can do the familar 如果我使用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);
}

but I realise that a race condition exists whereby if I remove the item from the map between the putIfAbsent and the get , the method above would return something that no longer exists in the collection. 但我意识到存在竞争条件 ,如果我从putIfAbsentget之间的地图中删除项目,上面的方法将返回集合中不再存在的东西。 This may or may not be fine, but lets assume that for my use case, it's not ok. 这可能会或可能不会很好,但我们假设对于我的用例,它不行。

What I'd really like is to have the whole thing atomic. 我真正喜欢的是让整个事物成为原子。 So, 所以,

public V getExampleTwo(K key) {
    return map.putIfAbsent(key, new Object());
}

but as this expands out to 但随着这扩大到

if (!map.containsKey(key))
   return map.put(key, value);     [1]
return map.get(key);

which for line [1] will return null for first usage (ie, map.put will return the previous value, which for first time use is null ). 对于第一次使用,第[1]行将返回null (即, map.put将返回先前的值,第一次使用时为null )。

I can't have it return null in this instance 我不能让它在这个实例中返回null

Which leaves me with something like; 这让我有类似的东西;

public V getExampleThree(K key) {
    Object object = new Object();
    V value = locks.putIfAbsent(key, object);
    if (value == null)
        return object;
    return value;
}

So, finally, my question; 最后,我的问题; how do the examples above differ in semantics?. 上面的例子在语义上有何不同? Does getExampleThree ensure atomicity like getExampleTwo but avoid the null return correctly? getExampleThree是否确保像getExampleTwo这样的原子性但是正确地避免了null返回? Are there other problems with getExampleThree ? getExampleThree还有其他问题吗?

I was hoping for a bit of discussion around the choices. 我希望对这些选择进行一些讨论。 I realise I could use a non ConcurrentHashMap and synchronize around clients calling my get method and a method to remove from the map but that seems to defeat the purpose (non blocking nature) of the ConcurrentHashMap. 我意识到我可以使用非ConcurrentHashMap并同步调用我的get方法的客户端和从地图中删除的方法,但这似乎打败了ConcurrentHashMap的目的(非阻塞性质)。 Is that my only choice to keep the data accurate ? 这是我保持数据准确的唯一选择吗?

I guess that's a bit part of why you'd choose ConcurrentHashMap; 我想这是你选择ConcurrentHashMap的原因之一; that its visible/up-to-date/acurrate at the point you interact with it, but there may be an impact further down the line if old data is going to be a problem... 它与您交互时的可见/最新/显示,但如果旧数据将成为一个问题,可能会对此产生影响......

It sounds like you are trying to create a global lock object for a key. 听起来您正在尝试为密钥创建全局锁定对象。

Instead of deleting an entry with the possibility of have it re-created almost immediately, I would only delete the entry when you pretty sure its not needed ever again. 我不会删除一个可能几乎立即重新创建的条目,而是当你非常确定它不再需要时,我只会删除该条目。

Otherwise, if you are using this for a lock, you can have two thread locking on different objects for the same key. 否则,如果您将其用于锁定,则可以在同一个键的不同对象上使用两个线程锁定。


If its not ok, you can busy loop it. 如果不行,你可以忙着循环它。

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;
}

it can still be removed or replaced as soon as the loop exists so its effectively much the same as. 一旦循环存在,它仍然可以被移除或替换,因此它实际上与它有效相同。

public V getExampleThree(K key) {
    Object o = new Object();
    map.putIfAbsent(key, o);
    Object ret = map.get(key);
    return ret == null ? o : ret;
}

So, finally, my question; 最后,我的问题; how do the examples above differ in semantics?. 上面的例子在语义上有何不同?

The difference is only the obvious. 差异只是显而易见的。

Does getExampleThree ensure atomicity like getExampleTwo but avoid the null return correctly? getExampleThree是否确保像getExampleTwo这样的原子性但是正确地避免了null返回?

Yes. 是。

Are there other problems with getExampleThree? getExampleThree还有其他问题吗?

Only if you believe the very next call might not give you a different value (if you believe it can be removed in another thread) 只有当你认为下次调用可能不会给你一个不同的值时(如果你认为它可以在另一个线程中删除)

The methods have different semantics: 这些方法有不同的语义:

  • getExampleOne is not atomic. getExampleOne不是原子的。
  • getExampleTwo returns null if the new object was inserted into the map. 如果将新对象插入到地图中,则getExampleTwo返回null。 This differs from the behavior of getExampleOne, but it is atomic. 这与getExampleOne的行为不同,但它是原子的。
  • getExampleThree is probably what you want. getExampleThree可能就是你想要的。 It is atomic and it return the object that is in the map after the point in time of the putIfAbsent call. 它是原子的,它会在putIfAbsent调用的时间点之后返回地图中的对象。 But it was problem when nulls are valid values in your application. 但是当nulls是您的应用程序中的有效值时,这是一个问题。 The null return value is then ambiguous. 那么空返回值是不明确的。

However, depending of the situation it might not be the actual object at the point in time when you use the return value. 但是,根据情况,它可能不是您使用返回值时的实际对象。 You then need explicit locking. 然后,您需要显式锁定。

Why not simply use the first version and synchronize the method? 为什么不简单地使用第一个版本并同步方法?

public synchronized V getExampleOne(K key) {
    map.putIfAbsent(key, new Object());
    return map.get(key);
}

While it won't provide you maximum parallelism, it's also true that you only have two operations and getExampleThree , while correct, is less readable and less easy to understand for someone else reading your code. 虽然它不会为您提供最大的并行性,但您也只有两个操作和getExampleThree ,而且正确,读取代码的其他人不太容易理解。

I think you will find the trick is to assume you will be non-atomic and handle it. 我想你会发现诀窍是假设你将是非原子的并处理它。

I am not really clear what you are looking for. 我不太清楚你在寻找什么。 Let me know if this is off at a tangent and I'll modify. 让我知道如果这是切断的,我会修改。

Perhaps you are looking for something like: 也许你正在寻找类似的东西:

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM