亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

僅在添加到 HashSet 時同步是線程安全的嗎?

僅在添加到 HashSet 時同步是線程安全的嗎?

九州編程 2023-05-17 16:56:16
想象一下,有一個主線程創建一個 HashSet 并啟動許多工作線程將 HashSet 傳遞給它們。就像下面的代碼一樣:void main() {  final Set<String> set = new HashSet<>();  final ExecutorService threadExecutor =   Executors.newFixedThreadPool(10);  threadExecutor.submit(() -> doJob(set));} void doJob(final Set<String> pSet) {  // do some stuff  final String x = ... // doesn't matter how we received the value.  if (!pSet.contains(x)) {    synchronized (pSet) {      // double check to prevent multiple adds within different threads      if (!pSet.contains(x)) {        // do some exclusive work with x.        pSet.add(x);      }    }  }  // do some stuff}我想知道僅在 add 方法上同步是否線程安全?contains如果不同步,是否有任何可能的問題?我的直覺告訴我這很好,在離開同步塊后,對 set 所做的更改應該對所有線程可見,但 JMM 有時可能是違反直覺的。附注:我不認為它與How to lock multiple resources in java multithreading重復 ,盡管對這兩個問題的回答可能相似,但這個問題解決了更多的特殊情況。
查看完整描述

3 回答

?
小唯快跑啊

TA貢獻1863條經驗 獲得超2個贊

我想知道僅在方法上同步是否線程安全add?contains如果不同步,是否有任何可能的問題?

簡短回答:否和是。

有兩種解釋方式:

直觀的解釋

Java 同步(以其各種形式)防止許多事情,包括:

  • 兩個線程同時更新共享狀態。

  • 一個線程試圖讀取狀態,而另一個正在更新它。

  • 線程看到過時的值,因為內存緩存尚未寫入主內存。

在您的示例中,同步add就足以確保兩個線程不能同時更新HashSet,并且兩個調用都將在最新HashSet狀態下運行。

但是,如果contains也不同步,則contains調用可能會與調用同時發生add。這可能導致contains調用看到 的中間狀態HashSet,從而導致不正確的結果,或者更糟。如果調用不是同時發生的,這也會發生,因為更改沒有立即刷新到主內存和/或讀取線程沒有從主內存讀取。

內存模型解釋

JLS 指定了 Java 內存模型,它規定了多線程應用程序必須滿足的條件,以保證一個線程可以看到另一個線程所做的內存更新。該模型是用數學語言表達的,并不容易理解,但要點是當且僅當從寫入到后續讀取之間存在一系列 happen before關系時,才能保證可見性。如果寫入和讀取在不同的線程中,那么線程之間的同步是這些關系的主要來源。例如在

 // thread one

 synchronized (sharedLock) {

    sharedVariable = 42;

 }


 // thread two

 synchronized (sharedLock) {

     other = sharedVariable;

 }

假設線程一的代碼在線程二的代碼之前運行,則線程一釋放鎖和線程二獲取鎖之間存在happens before關系。有了這個和“程序順序”的關系,我們就可以建立一個從寫入42到賦值到的鏈條other。這足以保證other將被分配42(或可能是變量的以后值)并且sharedVariable之前沒有任何值42被寫入它。


如果synchronized塊不在同一個鎖上同步,第二個線程可能會看到一個過時的值sharedVariable;即之前寫入的一些值42被分配給它。


查看完整回答
反對 回復 2023-05-17
?
森欄

TA貢獻1810條經驗 獲得超5個贊

該代碼對于該 synchronized (pSet) { }部分是線程安全的:


if (!pSet.contains(x)) {

  synchronized (pSet) { 

  // Here you are sure to have the updated value of pSet    

  if (!pSet.contains(x)) {

    // do some exclusive work with x.

    pSet.add(x);

  }

}

因為在synchronized對象的聲明中pSet:


一個且只有一個線程可能在這個塊中。

在其中,pSet它的更新狀態也由與 synchronized 關鍵字的 happens-before 關系保證。

因此,無論等待線程的第一個語句返回的值是什么if (!pSet.contains(x)),當這個被等待的線程醒來并進入語句時 synchronized,它都會設置最后更新的值pSet。因此,即使前一個線程添加了相同的元素,第二個線程 if (!pSet.contains(x))也會返回false。


但是這段代碼對于if (!pSet.contains(x))在寫入Set.

根據經驗,不應該使用未設計為線程安全的集合來執行并發的寫入和讀取操作,因為集合的內部狀態可能處于正在進行/不一致的狀態,以進行同時發生的讀取操作一個寫操作。

雖然一些非線程安全的集合實現在事實中接受了這樣的用法,但這根本不能保證它總是正確的。

所以你應該使用線程安全的Set實現來保證整個線程安全。

例如:


Set<String> pSet = ConcurrentHashMap.newKeySet();

這在引擎蓋下使用 a ConcurrentHashMap,因此沒有讀取鎖和最小的寫入鎖(僅在要修改的條目上而不是整個結構上)。


查看完整回答
反對 回復 2023-05-17
?
臨摹微笑

TA貢獻1982條經驗 獲得超2個贊

,

您不知道在另一個線程添加期間哈希集可能處于什么狀態。可能正在進行根本性的更改,例如存儲桶的拆分,因此在另一個線程添加期間包含可能會返回false ,即使該元素將存在于單線程 HashSet 中。在那種情況下,您將嘗試第二次添加元素。

更糟糕的情況:由于兩個線程同時使用的內存中的 HashSet 處于臨時無效狀態,contains可能會陷入死循環或拋出異常。


查看完整回答
反對 回復 2023-05-17
  • 3 回答
  • 0 關注
  • 207 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號