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



我正在制作一个应用程序,它需要一堆日志条目并计算sum。

当有多个线程调用addToSum()方法时,

下面的方法是线程/并发安全的。我要确保每次调用都正确地更新总数。

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

我需要synchronize get/put还是有更好的方法?

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

非常感谢!

更新:

感谢大家的回答,我已经知道上面的代码不是线程安全的

感谢Vint建议AtomicReference替代synchronize。我以前使用AtomicInteger来保存整数和,我想知道BigDecimal是否有类似的东西。

是关于两者利弊的决定性结论吗?

你可以像其他人建议的那样使用synchronized,但是如果你想要一个最小阻塞的解决方案,你可以尝试将AtomicReference作为BigDecimal的存储

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;
    }
}

编辑-我会解释更多:

AtomicReference使用CAS自动分配单个引用。循环是这样写的

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

的BigDecimal对象。

你想在这里使用一个循环,因为有可能有两个线程试图添加到同一个AtomicReference。可能会发生一个线程成功而另一个线程失败的情况,如果发生这种情况,只需使用新添加的值再次尝试。

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

您的解决方案不是线程安全的。原因是,由于put操作与get操作是分开的,因此可能会遗漏求和(因此,您放入映射中的新值可能会错过同时添加的和)。

最安全的方法是同步你的方法。

这是安全的,因为线程A和B可能同时调用sumByAccount.get(account)(或多或少),所以没有一个会看到对方的add(amount)的结果。也就是说,事情可能按照以下顺序发生:

  • 线程A调用sumByAccount.get("accountX"),得到(例如)10.0。
  • 线程B调用sumByAccount.get("accountX")并获得与线程A相同的值:10.0。
  • 线程A设置newSum为10.0 + 2.0 = 12.0。
  • 线程B将其newSum设置为(例如)10.0 + 5.0 = 15.0。
  • 线程A调用sumByAccount.put("accountX", 12.0)
  • 线程B调用sumByAccount.put("accountX", 15.0),覆盖线程A所做的。
解决这个问题的一种方法是将synchronized放在addToSum方法上,或者将其内容包装在synchronized(this)synchronized(sumByAccount)中。由于上述事件序列仅在两个线程同时更新同一个帐户时才会发生,因此另一种方法可能是基于某种Account对象进行外部同步。如果没有看到程序逻辑的其余部分,我无法确定。

是的,你需要同步,否则你可以有两个线程每个获得相同的值(对于相同的键),说A和线程1添加B到它,线程2添加C到它并将其存储回来。现在的结果将不是A+B+C,而是A+B或A+C。

你需要做的是锁定加法的共同之处。同步get/put没有帮助,除非您执行

synchronize {
    get
    add
    put
}

,但如果你这样做,你会阻止线程更新值,即使它是不同的键。您需要对该帐户进行同步。然而,在字符串上同步似乎不安全,因为它可能导致死锁(您不知道还有什么东西锁住了字符串)。你能不能创建一个account对象,用它来锁?

相关内容

  • 没有找到相关文章

最新更新