【问题标题】:Populating map from multiple threads从多个线程填充映射
【发布时间】:2015-12-21 22:14:38
【问题描述】:

我有一个 ConcurrentHashMap,我从多个线程中填充,如下所示:

private static Map<ErrorData, Long> holder = new ConcurrentHashMap<ErrorData, Long>();

public static void addError(ErrorData error) {
    if (holder.keySet().contains(error)) {
        holder.put(error, holder.get(error) + 1);
    } else {
        holder.put(error, 1L);
    }
}

上面的代码中是否存在竞争条件并且可以跳过更新?另外,如果可以提供更好的性能,我该如何在这里使用 Guava AtomicLongMap

我使用的是 Java 7。

【问题讨论】:

    标签: java multithreading guava concurrenthashmap


    【解决方案1】:

    是的,因为您没有自动检查包含和放置,所以可能会出现竞争。

    您可以按如下方式使用AtomicLongMap,它会自动检查:

    private static final AtomicLongMap<ErrorData> holder = AtomicLongMap.create();
    
    public static void addError(ErrorData error) {
      holder.getAndIncrement(error);
    }
    

    如 javadoc 中所述:

    [T]写入此映射的典型机制是 addAndGet(K, long),它向当前与 K 关联的值添加 long。如果键尚未与值关联,则其隐含值为零。

    除非另有说明,否则所有操作都是原子操作。

    【讨论】:

    • 如果你看到我的例子,我在地图中也有一个 Long 值,我正在为每个唯一键递增。最后我打印出这张地图,它像这样打印出来@ 987654323@
    • @user1950349,是吗?这就说得通了;你仍然可以使用AtomicLongMap
    • 顺便说一句,我在初始化AtomicLongMap时看到了这个错误:Cannot infer type arguments for AtomicLongMap&lt;&gt;
    • @user1950349 AtomicLongMap 就像Map&lt;ErrorData, Long&gt;。您可以通过调用holder.asMap() 获得实际的Map&lt;ErrorData, Long&gt;
    • 我明白你的意思...让我玩一下它。它有点新,所以我很困惑..谢谢你的解释。
    【解决方案2】:

    如果您使用的是 java 8,则可以利用新的 merge 方法:

    holder.merge(error, 1L, Long::sum);
    

    【讨论】:

    • 我还在使用 Java 7。忘了提.. 让我更新一下我的问题。
    【解决方案3】:

    “香草”Java 5+ 解决方案:

    public static void addError(final ErrorData errorData) {
        Long previous = holder.putIfAbsent(errorData, 1L);
        // if the error data is already mapped to some value
        if (previous != null) {
            // try to replace the existing value till no update takes place in the meantime
            while (!map.replace(errorData, previous, previous + 1)) {
                previous = map.get(errorData);
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      在 Java 7 或更早的版本中,您需要使用比较和更新循环:

      Long prevValue;
      boolean done;
      do {
        prevValue = holder.get(error);
        if (prevValue == null) {
          done = holder.putIfAbsent(error, 1L);
        } else {
          done = holder.replace(error, prevValue, newValue);
        }
      } while (!done);
      

      使用此代码,如果两个线程竞争,其中一个可能最终会重试其更新,但它们最终会得到正确的值。

      考虑:

      Thread1: holder.get(error) returns 1
      Thread2: holder.get(error) returns 1
      Thread1: holder.put(error, 1+1);
      Thread2: holder.put(error, 1+1);
      

      要解决此问题,您需要使用原子操作来更新地图。

      【讨论】:

      • 这不是和我的回答一样吗?
      • @Bax 是的,但我没有注意到你已经发布了......我刚刚看到了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-03
      • 1970-01-01
      相关资源
      最近更新 更多