如何使在 ConcurrentHashMap 线程中更新 BigDecimal 是安全的

我正在制作一个应用程序,该应用程序需要一堆日记帐分录并计算总和。

下面的方式是线程/并发安全,当有多个线程调用该方法时。我想确保每个呼叫都正确更新总数。addToSum()

如果不安全,请说明我必须做些什么来确保线程安全。

我需要获取/放置还是有更好的方法?synchronize

private ConcurrentHashMap<String, BigDecimal> sumByAccount;

public void addToSum(String account, BigDecimal amount){
    BigDecimal newSum = sumByAccount.get(account).add(amount);
    sumByAccount.put(account, newSum);
}

非常感谢!

更新:

谢谢大家的答案,我已经得到上面的代码不是线程安全的

感谢 Vint 建议作为 .我以前用来保存整数和,我想知道BigDecimal是否有类似的东西。AtomicReferencesynchronizeAtomicInteger

关于两者的利弊是一个明确的结论吗?


答案 1

您可以像其他人建议的那样使用同步,但是如果需要一个最小阻止的解决方案,您可以尝试作为BigDecimal的存储AtomicReference

ConcurrentHashMap<String,AtomicReference<BigDecimal>> map;

public void addToSum(String account, BigDecimal amount) {
    AtomicReference<BigDecimal> newSum = map.get(account);
    for (;;) {
       BigDecimal oldVal = newSum.get();
       if (newSum.compareAndSet(oldVal, oldVal.add(amount)))
            return;
    }
}

编辑 - 我会对此进行更多解释:

原子引用使用 CAS 以原子方式分配单个引用。循环中是这样说的。

如果存储在 AtomicReference 中的当前字段 == [它们在内存中的位置,而不是它们的值],则将存储在 AtomicReference 中的字段的值替换为 。现在,在 for 循环之后的任何时候,您调用 newSum.get() 时,它都会有已添加到的 BigDecimal 对象。oldValoldVal.add(amount)

您希望在此处使用循环,因为可能有两个线程正在尝试添加到同一个 AtomicReference 中。一个线程成功而另一个线程失败,如果发生这种情况,只需使用新的附加值重试即可。

对于中等线程争用,这将是一个更快的实现,对于高争用,您最好使用synchronized


答案 2

您的解决方案不是线程安全的。原因是由于要放置的操作与要获取的操作是分开的,因此可能会错过总和(因此,您放入地图中的新值可能会错过同时添加的总和)。

执行所需操作的最安全方法是同步方法。


推荐