【问题标题】:Atomically perform multiple operations原子地执行多个操作
【发布时间】:2013-03-19 13:38:16
【问题描述】:

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

我的逻辑是这样的:

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

    doSomethingElse();
}

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

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

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

【问题讨论】:

  • 所以你想在doSomethingElse被执行之前阻止线程访问映射(并看到新的键)?
  • 是的。因为,如果我在执行doSomethingElse 之前让其他线程看到新密钥,它可能也会调用doSomethingElse,这将启动一个单独的线程。
  • 但是如果你使用putIfAbsent而不是if !contains then put,那么只有一个put call会成功——这能解决争用吗?
  • @SteveTownsend 我认为你可能有一个有效的观点!
  • 可以使用类似a lock-free memoizer 的模式吗?您可能有一个 ValueHolder 类来保存值和计算。在 putIfAbsent 之后,您可以开始计算(如果它已经运行,则不会执行任何操作)。

标签: java multithreading concurrency


【解决方案1】:

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

如果是这种情况,您通常必须在外部进行同步。

在某些情况下(取决于 doSomethingElse() 期望映射的状态是什么,以及其他线程可能会执行映射什么),以下方法也可能起作用:

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

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

【讨论】:

  • 没错,但它不会首先破坏使用ConcurrentHashMap 的目的吗?
  • 是的,但是使用if !contains then putConcurrentHashMap 来说不是惯用的,这可能是你的问题
  • @adarshr - 它可能会破坏 你的 目的......但那是因为你期望 CHM 做一些它显然没有/不能做的事情。
【解决方案2】:

除非您希望所有放置线程都等到第一个成功的线程放入映射中,否则这将起作用..

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

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

现在,如果多个线程使用相同的key,则只有一个会获胜,而只有一个会doSomethingElse()

【讨论】:

  • 是的,这正是我想要的行为。 doSomethingElse 必须仅在 put 成功时执行,无论它是哪个线程。其他线程必须简单地从if 中掉出来。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    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 并拥有一个接受 RunnableputIfAbsent 版本(如果放置元素则执行)。但这会复杂得多。

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

    【讨论】:

    • 为什么我不能只使用if (map.putIfAbsent(key, ref)==null) doSomethingElse();?我认为它仍然可以,因为只有成功执行putIfAbsent 的线程也可以执行doSomethingElse 方法。换句话说,让它成为原子的。
    • 因为doSomethingElse 不是原子的,而另一个线程可以在您完成其他操作之前get(key)(即它在put 之后立即看到中间状态)。
    • 是的,这就是重点。另一个线程不能调用doSomethingElse,除非它是在putIfAbsent(key) 中成功的线程。如果我遗漏了什么,请纠正我。
    • @adarshr - 你没有遗漏任何东西
    • T1:putIfAbsent("foo",1),成功; T1:开始doSomethingElse; T2:get("foo") 返回 1; T1:继续doSomethingElse。如果doSomethingElsemap.put("foo",map.get("foo")+1) 会发生什么? (即操作不会是原子的)
    猜你喜欢
    • 2013-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-20
    相关资源
    最近更新 更多