简体   繁体   English

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

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

I am making an application that takes a bunch of journal entries and calculate sum. 我正在制作一个应用程序,它需要一堆日记帐分录并计算总和。

Is below way of doing it is thread/concurrency safe when there are multiple threads calling the addToSum() method. 当有多个线程调用addToSum()方法时,下面的方法是线程/并发安全。 I want to ensure that each call updates the total properly. 我想确保每次通话都能正确更新总数。

If it is not safe, please explain what do I have to do to ensure thread safety. 如果不安全,请说明我必须做些什么来确保螺纹安全。

Do I need to synchronize the get/put or is there a better way? 我是否需要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);
}

Thanks so much! 非常感谢!

Update: 更新:

Thanks everyone for the answer, I already get that the code above is not thread-safe . 感谢大家的回答,我已经知道上面的代码不是线程安全的

Thanks Vint for suggesting the AtomicReference as an alternative to synchronize . 感谢Vint建议将AtomicReference作为synchronize的替代方法。 I was using AtomicInteger to hold integer sums before and I was wondering if there are something like that for BigDecimal. 我以前使用AtomicInteger来保存整数和,我想知道BigDecimal是否有类似的东西。

Is the a definitive conclusion on the pro and con of the two? 关于两者的赞成和反对,这是一个明确的结论吗?

You can use synchronized like the others suggested but if want a minimally blocking solution you can try AtomicReference as a store for the 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;
    }
}

Edit - I'll explain this more: 编辑 - 我会解释更多:

An AtomicReference uses CAS to atomically assigns a single reference. AtomicReference使用CAS以原子方式分配单个引用。 The loop says this. 循环说明了这一点。

If the current field stored in AtomicReference == oldVal [their location in memory, not their value] then replace the value of the field stored in AtomicReference with oldVal.add(amount) . 如果当前字段存储在AtomicReference == oldVal [它们在内存中的位置,而不是它们的值],则用oldVal.add(amount)替换存储在AtomicReference中的字段的值。 Now, any time after the for-loop you invoke newSum.get() it will have the BigDecimal object that has been added to. 现在,在for循环之后的任何时候你调用newSum.get()它将具有已添加到的BigDecimal对象。

You want to use a loop here because it is possible two threads are trying to add to the same AtomicReference. 你想在这里使用一个循环,因为有两个线程可能试图添加到同一个AtomicReference。 It can happen that one thread succeeds and another thread fails, if that happens just try again with the new added value. 可能会发生一个线程成功而另一个线程失败,如果发生这种情况,只需再次使用新增值。

With moderate thread contention this would be a faster implementation, with high contention you are better off using synchronized 如果中等线程争用,这将是一个更快的实现,高争用你最好使用synchronized

That is not safe, because threads A and B might both call sumByAccount.get(account) at the same time (more or less), so neither one will see the result of the other's add(amount) . 这是不是安全的,因为线程A和B可能都调用sumByAccount.get(account) ,同时(或多或少),所以没有人会看到其他的结果add(amount) That is, things might happen in this sequence: 也就是说,事情可能会按以下顺序发生:

  • thread A calls sumByAccount.get("accountX") and gets (for example) 10.0. 线程A调用sumByAccount.get("accountX")并获取(例如)10.0。
  • thread B calls sumByAccount.get("accountX") and gets the same value that thread A did: 10.0. 线程B调用sumByAccount.get("accountX")并获得与线程A相同的值:10.0。
  • thread A sets its newSum to (say) 10.0 + 2.0 = 12.0. 线程A将其newSum设置为(例如)10.0 + 2.0 = 12.0。
  • thread B sets its newSum to (say) 10.0 + 5.0 = 15.0. 线程B将其newSum设置为(比方说)10.0 + 5.0 = 15.0。
  • thread A calls sumByAccount.put("accountX", 12.0) . 线程A调用sumByAccount.put("accountX", 12.0)
  • thread B calls sumByAccount.put("accountX", 15.0) , overwriting what thread A did. 线程B调用sumByAccount.put("accountX", 15.0) ,覆盖线程A所做的事情。

One way to fix this is to put synchronized on your addToSum method, or to wrap its contents in synchronized(this) or synchronized(sumByAccount) . 解决此问题的一种方法是在您的addToSum方法上放置synchronized ,或者将其内容包装在synchronized(this)synchronized(sumByAccount) Another way, since the above sequence of events only happens if two threads are updating the same account at the same time, might be to synchronize externally based on some sort of Account object. 另一种方式,因为上述事件序列仅在两个线程同时更新同一帐户时才会发生,可能是基于某种Account对象在外部进行同步。 Without seeing the rest of your program logic, I can't be sure. 没有看到你的程序逻辑的其余部分,我不能确定。

Your solution is not thread safe. 您的解决方案不是线程安全的。 The reason is that it is possible for a sum to be missed since the operation to put is separate from the operation to get (so the new value you are putting into the map could miss a sum that is being added at the same time). 原因是由于要进行的操作与要获取的操作是分开的,因此可能会丢失一个总和(因此您放入地图的新值可能会错过同时添加的总和)。

The safest way to do what you want to do is to synchronize your method. 做你想做的最安全的方法是同步你的方法。

Yes, you need to synchronize since otherwise you can have two threads each getting the same value (for the same key), say A and thread 1 add B to it and thread 2 adds C to it and store it back. 是的,你需要同步,否则你可以有两个线程,每个线程获得相同的值(对于相同的键),比如A和线程1将B添加到它,线程2将C添加到它并将其存储回来。 The result now will not be A+B+C, but A+B or A+C. 结果现在不是A + B + C,而是A + B或A + C.

What you need to do is lock on something that is common to the additions. 你需要做的是锁定添加中常见的东西。 Synchronizing on get/put will not help, unless you do 除非你这样做,否则在get / put上同步将无济于事

synchronize {
    get
    add
    put
}

but if you do that then you will prevent threads from updating values even if it is for different keys. 但是,如果你这样做,那么你将阻止线程更新值,即使它是针对不同的键。 You want to synchronize on the account. 您想要在帐户上同步。 However, synchronizing on the string seems unsafe as it could lead to deadlocks (you don't know what else locks the string). 但是,对字符串进行同步似乎不安全,因为它可能导致死锁(您不知道还有什么锁定字符串)。 Can you create an account object instead and use that for locking? 您可以改为创建一个帐户对象并将其用于锁定吗?

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

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