![](/img/trans.png)
[英]Updating BigDecimal concurrently within ConcurrentHashMap thread safe
[英]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)
。 也就是說,事情可能會按以下順序發生:
sumByAccount.get("accountX")
並獲取(例如)10.0。 sumByAccount.get("accountX")
並獲得與線程A相同的值:10.0。 newSum
設置為(例如)10.0 + 2.0 = 12.0。 newSum
設置為(比方說)10.0 + 5.0 = 15.0。 sumByAccount.put("accountX", 12.0)
。 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.