簡體   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