原子执行多个操作

问题描述:

我试图找到一种方法来以原子方式在ConcurrentHashMap上执行多个操作。原子执行多个操作

我的逻辑是这样的:

if (!map.contains(key)) { 
    map.put(key, value); 

    doSomethingElse(); 
} 

我知道有是putIfAbsent方法。但是如果我使用它,我仍然无法以原子方式调用doSomethingElse

除了诉诸同步/客户端锁定之外,还有什么办法可以做到吗?

如果有帮助,在我的情况下,doSomethingElse会非常复杂,涉及创建和启动一个线程,查找我们刚刚添加到地图中的键。

+0

所以你想在执行doSomethingElse之前阻止线程访问映射(并且看到新的键)? – assylias 2013-03-19 13:43:12

+0

是的。因为如果我让另一个线程在执行'doSomethingElse'之前看到新的键值,它可能会调用'doSomethingElse'也会启动一个单独的线程。 – adarshr 2013-03-19 13:45:05

+1

但是,如果您使用'putIfAbsent'而不是'if!contains then put',则只有一个投入呼叫会成功 - 这是否解决了争用? – 2013-03-19 13:46:48

这会工作,除非你想要把所有的线程等待,直到第一个成功的线程放入地图..

if(map.get(key) == null){ 

    Object ret = map.putIfAbsent(key,value); 
    if(ret == null){ // I won the put 
    doSomethingElse(); 
    } 
} 

现在,如果许多线程具有相同key唯一一个把赢,只有一个将doSomethingElse()

+1

是的,那正是我想要的行为。 'doSomethingElse'只能在投放成功时执行,无论它是哪个线程。其他线程必须简单地掉出'if'。 – adarshr 2013-03-19 14:03:28

如果有帮助,在我的情况下,doSomethingElse会非常复杂,涉及创建和启动一个线程,该线程会查找我们刚刚添加到地图中的键。

如果是这样的话,您通常必须进行外部同步。

在某些情况下(这取决于doSomethingElse()预计地图的状态是,什么其他线程可能会做地图),以下也可工作:

if (map.putIfAbsent(key, value) == null) { 
    doSomethingElse(); 
} 

这将确保只对于任何给定的密钥,一个线程进入doSomethingElse()

+0

确实如此,但是它不会破坏首先使用'ConcurrentHashMap'的目的吗? – adarshr 2013-03-19 13:46:43

+0

是的,但是使用'if!contains then put'''' ConcurrentHashMap'不是惯用的,这可能是你的问题 – 2013-03-19 13:47:47

+2

@adarshr - 它可能会破坏你的**目的......但那是因为你期望CHM做一些显然不能/不能做的事情。 – 2013-03-19 13:48:12

如果您的设计要求将地图访问和其他操作分组,而没有其他人访问地图,则您别无选择,只能将其锁定。也许设计可以重新审视以避免这种需求?

这也意味着对地图的所有其他访问都必须在同一个锁后面进行序列化。

您可能每个条目都保留锁定。这将允许并发非锁定更新,除非两个线程尝试访问相同的元素。

class LockedReference<T> { 
    Lock lock = new ReentrantLock();; 
    T value; 
    LockedReference(T value) {this.value=value;}  
} 

LockedReference<T> ref = new LockedReference(value); 
ref.lock.lock(); //lock on the new reference, there is no contention here 
try { 
    if (map.putIfAbsent(key, ref)==null) { 
    //we have locked on the key before inserting the element 
    doSomethingElse(); 
    } 
} finally {ref.lock.unlock();} 

以后

Object value; 
while (true) { 
    LockedReference<T> ref = map.get(key) 
    if (ref!=null) { 
     ref.lock.lock(); 
     //there is no contention, unless a thread is already working on this entry 
     try { 
     if (map.containsKey(key)) { 
      value=ref.value; 
      break;  
     } else { 
      /*key was removed between get and lock*/ 
     } 
     } finally {ref.lock.unlock();} 
    } else value=null; 
} 

甲发烧友的方法将是重写ConcurrentHashMap和有一个版本的putIfAbsent接受一个Runnable(如果该元件置于其上执行)。但是这远远更复杂。

基本上,ConcurrentHashMap实现了锁定段,它位于每个条目的一个锁定和整个地图的一个全局锁定之间。

+0

为什么我不能只使用'if(map.putIfAbsent(key,ref)== null)doSomethingElse();'?我认为它仍然没问题,因为只有成功执行'putIfAbsent'的线程才能执行'doSomethingElse'方法。换句话说,使其成为原子。 – adarshr 2013-03-19 14:05:46

+0

因为'doSomethingElse'不会是原子的,而另一个线程在完成其他操作之前(即在'put'之后立即看到中间状态)可以'获得(键)'。 – Javier 2013-03-19 14:08:52

+0

是的,就是这一点。另一个线程不能调用'doSomethingElse',除非它是'putIfAbsent(key)'中成功的那个线程。请纠正我,如果我失去了一些东西。 – adarshr 2013-03-19 14:11:37