【问题标题】:How to handle concurrency in this case?在这种情况下如何处理并发?
【发布时间】:2015-08-20 06:40:46
【问题描述】:

我有一个 HashMap

ConcurrentHashMap<String, Integer> count =new ConcurrentHashMap<String, Integer>();

我会这样使用:

private Integer somefunction(){
    Integer order;
    synchronized (this) {
          if (count.containsKey(key)) {
            order = count.get(key);
            count.put(key, order + 1);
          } else {
            order = 0;
            count.put(key, order + 1);
          }
        }
    return order;
}

但是正如你所看到的,这对于处理并发可能并不理想,因为只有同一个 key 下的 value 可能会相互干扰。不同的 key 不会相互干扰,所以不需要同步所有操作。我只想在密钥相同的情况下进行同步。

我可以做一些可以实现更好的并发性能的事情吗? (我知道 ConcurrentHashMap 和 synchronize 在这里有点多余,但让我们关注我们是否只能在 key 相同时进行同步)

【问题讨论】:

  • 你能解释一下你想要达到的目标吗?
  • 不同键互不干扰,不需要同步所有操作。我只想在密钥相同时进行同步。
  • 看起来您想要的算法是“如果地图中不存在键,则将其插入顺序值为 1,并返回 0。如果该项目存在于地图中,则增加其顺序值,并返回增量前的值。”这是正确的吗?
  • 正是我想做的事

标签: java multithreading concurrency hashmap java.util.concurrent


【解决方案1】:

ConcurrentHashMap 的全部意义在于方便并发操作。以下是无需显式同步即可进行原子更新的方法:

private Integer somefunction() {
    Integer oldOrder;
    // Insert key if it isn't already present.
    oldOrder = count.putIfAbsent(key, 1);
    if (oldOrder == null) {
        return 0;
    }
    // If we get here, oldOrder holds the previous value.
    // Atomically update it.
    while (!count.replace(key, oldOrder, oldOrder + 1)) {
        oldOrder = count.get(key);
    }
    return oldOrder;
}

有关详细信息,请参阅 putIfAbsent()replace() 的 Javadocs。

正如Tagir Valeev 指出的in his answer,如果您使用的是Java 8,则可以使用merge(),这会将上面的代码缩短为:

private Integer somefunction() {
    return count.merge(key, 1, Integer::sum) - 1;
}

另一种选择是让值改为AtomicInteger。请参阅hemant1900's answer 了解如何操作。

【讨论】:

  • 返回 0;返回旧订单;
【解决方案2】:

我认为这可能会更好更简单 -

private final ConcurrentHashMap<String, AtomicInteger> count = new ConcurrentHashMap<String, AtomicInteger>();

private Integer someFunction(String key){        
    AtomicInteger order = count.get(key);
    if (order == null) {
        final AtomicInteger value = new AtomicInteger(0);
        order = count.putIfAbsent(key, value);
        if (order == null) {
            order = value;
        }
    }
    return order.getAndIncrement();
}

【讨论】:

    【解决方案3】:

    如果你会使用 Java-8,那就很容易了:

    return count.merge(key, 1, Integer::sum)-1;
    

    不需要额外的同步。 merge 方法保证原子执行。

    【讨论】:

      【解决方案4】:

      首先,key 是从哪里来的?

      其次,如果在任何时候运行该函数的两个线程的键永远不会相同,则您不需要同步该函数的任何部分。

      但是,如果两个线程可以同时拥有相同的密钥,那么您只需要:

      synchronized(count) {
         count.put(key, order + 1);
      }
      

      这样做的原因是只有对象变量的线程突变需要同步。但是您使用ConcurrentHashMap这一事实应该消除这个问题(仔细检查我),因此不需要同步。

      【讨论】:

        【解决方案5】:

        这是我的做法,

        private Integer somefunction(){
          Integer order = count.compute(key, (String k, Integer v) -> {
              if (v == null)
                return 1;
              else {
                return v + 1;
              }
            });
          return order-1;
        }
        

        这避免了不断尝试使用 replace(key,oldValue,newValue) 这对并发性会更好吗?

        问题是很多环境还不支持jdk8。

        【讨论】:

        • 如果你要依赖 Java 8,@tagir-valeev 的建议在这里使用 merge() 似乎比 compute() 更好。
        猜你喜欢
        • 2014-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-12
        • 2021-08-01
        • 1970-01-01
        相关资源
        最近更新 更多