原子地执行多个操作

时间:2013-03-19 13:38:17

标签: java multithreading concurrency

我正试图找到一种以原子方式对ConcurrentHashMap执行多个操作的方法。

我的逻辑是这样的:

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

    doSomethingElse();
}

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

除了采用同步/客户端锁定之外,还有什么方法可以做这些事情吗?

如果有帮助,我的案例中的doSomethingElse会非常复杂,包括创建和启动一个线程,查找我们刚刚添加到地图中的键。

4 个答案:

答案 0 :(得分:5)

  

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

如果是这种情况,通常需要在外部进行同步。

在某些情况下(取决于doSomethingElse()期望地图的状态,以及其他线程可能对地图执行的操作),以下内容也可能有效:

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

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

答案 1 :(得分:3)

除非你想让所有的线程都等到第一个成功的线程放入地图,否则这将有效。

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

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

现在,如果许多线程使用相同的key,只有一个会赢,只有一个会doSomethingElse()

答案 2 :(得分:2)

如果您的设计要求在没有其他人访问地图的情况下对地图访问和其他操作进行分组,那么您别无选择,只能锁定它们。也许可以重新设计以避免这种需求?

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

答案 3 :(得分:1)

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

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实现了锁定的段,它位于每个条目一个锁之间的中间位置,以及整个映射的一个全局锁。