繁体   English   中英

如何在ConcurrentHashMap线程安全的情况下更新BigDecimal

[英]How to make updating BigDecimal within ConcurrentHashMap thread safe

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

当有多个线程调用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 [它们在内存中的位置,而不是它们的值],则用oldVal.add(amount)替换存储在AtomicReference中的字段的值。 现在,在for循环之后的任何时候你调用newSum.get()它将具有已添加到的BigDecimal对象。

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

如果中等线程争用,这将是一个更快的实现,高争用你最好使用synchronized

这是不是安全的,因为线程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所做的事情。

解决此问题的一种方法是在您的addToSum方法上放置synchronized ,或者将其内容包装在synchronized(this)synchronized(sumByAccount) 另一种方式,因为上述事件序列仅在两个线程同时更新同一帐户时才会发生,可能是基于某种Account对象在外部进行同步。 没有看到你的程序逻辑的其余部分,我不能确定。

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

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

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

你需要做的是锁定添加中常见的东西。 除非你这样做,否则在get / put上同步将无济于事

synchronize {
    get
    add
    put
}

但是,如果你这样做,那么你将阻止线程更新值,即使它是针对不同的键。 您想要在帐户上同步。 但是,对字符串进行同步似乎不安全,因为它可能导致死锁(您不知道还有什么锁定字符串)。 您可以改为创建一个帐户对象并将其用于锁定吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM