[英]Do we need to make ConcurrentHashMap volatile?
我們有一個共享的ConcurrentHashMap
,它由 2 個線程讀取和寫入。
class Test {
private ConcurrentHashMap<Object, Object> map = new ConcurrentHashMap<>();
Object read() {
return map.get(object);
}
void write(Object key, Object object) {
map.put(key, object);
}
}
我們是否需要使映射可變,以便讀取器線程盡快看到一個線程的寫入?
是否有可能在另一個線程中看不到或很晚看到一個線程中的地圖放置?
HashMap
相同問題。
如果你能做到final
那就去做吧。 如果你不能使它成為final
那么是的,你需要使它成為volatile
。 volatile
適用於字段分配,如果它不是final
則有可能(至少根據 JMM)一個線程對 CHM 字段的寫入可能對另一個線程不可見。 重申一下,這是ConcurrentHashMap
字段分配,而不是使用 CHM。
話雖如此,你真的應該讓它成為final
。
我們是否需要使映射可變,以便讀取器線程盡快看到一個線程的寫入?
如果您所說的寫入是使用 CHM 本身的變異方法(如put
或remove
)完成的,那么您使字段變得易變不會產生影響。 所有內存可見性保證都在 CHM 內完成。
是否有可能在另一個線程中看不到或很晚看到一個線程中的地圖放置?
HashMap
相同問題。
不適用於ConcurrentHashMap
。 如果您同時使用普通的HashMap
,請不要。 見: http : //mailinator.blogspot.com/2009/06/beautiful-race-condition.html
volatile
在對相應變量的讀取和寫入時應用happens-before 語義。
一個字段可以聲明為
volatile
,在這種情況下,Java 內存模型確保所有線程看到變量的一致值(第 17.4 節)。
它與變量值引用的對象無關。 你沒有修改變量,所以你不應該*有任何問題,除非(*)你沒有安全地發布跨線程共享的Test
對象。
正如Lii 在評論中所建議的,假設您沒有通過final
、 volatile
或其他一些同步機制采取正確的預防措施,JMM 允許在對象被其構造函數完全初始化之前提供對對象的引用. 因此,您的一個線程可能會在初始化之前嘗試使用map
字段(例如,它會看到null
)。 從這個意義上說,代碼可能會中斷。
是否有可能在另一個線程中看不到或很晚看到一個線程中的地圖放置?
這是不可能的,正如javadoc所述, ConcurrentHashMap
方法引入了適當的內存屏障,
檢索反映了最近完成的更新操作的結果。 (更正式地,給定鍵的更新操作與報告更新值的該鍵的任何(非空)檢索具有發生前關系。
但是, HashMap
不是線程安全類型。 volatile
在這里也無濟於事,因為它控制對變量的更改,而不是變量引用的對象。 您將需要外部同步來保護put
和get
對HashMap
調用。
這里有 2 個子問題:參考地圖的可見性和寫入地圖的值的可見性。
我們需要制作地圖嗎...
因此,在您的情況下,您的“地圖”參考未正確發布。 這可能會導致 Test.read() 或/和 Test.write() 中的 NullPointerException (這取決於哪個線程實例化 ConcurrentHashMap 並將其放入“map”字段)。 正確的代碼將是以下之一:
//1. Provide access to the reference through a properly locked field class Test { ConcurrentHashMap map; synchronized void init(ConcurrentHashMap map) { this.map = map; } synchronized void read() { map.get(object); } synchronized void write() { map.put(key, object); } } // or class Test { ReadWriteLock rwl = new ReentrantReadWriteLock(); ConcurrentHashMap map; void init(ConcurrentHashMap map) { rwl.writeLock().lock(); this.map = map; rwl.writeLock().release(); } void read() { rwl.readLock().lock(); try { map.get(object); } finally { rwl.readLock().release(); } } void write() { rwl.writeLock().lock(); try { map.put(key, object); } finally { rwl.writeLock().release(); } } } // 3. Provide access to the reference via a volatile field class Test { volatile ConcurrentHashMap map; // or AtomicReference<ConcurrentHashMap> map = new AtomicReference(); void init(ConcurrentHashMap map) { this.map = map; } void read() { map.get(object); } void write() { map.put(key, object); } } // 4. Initialize the value as a final field class Test { final ConcurrentHashMap map; Test(ConcurrentHashMap map) { this.map = map; } void read() { map.get(object); } void write() { map.put(key, object); } }
當然,您可以在 p.1 的情況下使用普通 HashMap(當您使用正確鎖定的字段“map”時)而不是 ConcurrentHashMap。 但是,如果您仍然想使用 ConcurrentHashMap 來獲得更好的性能,那么正確發布“地圖”的最佳方法,如您所見,是使該字段成為最終版本。
這是一篇 Oracle 人員關於安全發布的好文章,順便說一句: http : //shipilev.net/blog/2014/safe-public-construction/
是否有可能在另一個線程中看不到或很晚看到一個線程中的地圖放置?
不,如果您沒有獲得 NPE(請參閱第 1 頁)或正確發布了您的地圖,則讀者始終會看到作者產生的所有更改,因為一對 ConcurrentHashMap.put/get 會產生適當的內存屏障/Happens-Before邊緣。
HashMap 的相同問題
HashMap 根本不是線程安全的。 方法 HashMap.put/get 以非線程安全的方式處理映射的內部狀態(非原子,不保證更改狀態的線程間可見性),因此,您可能只是破壞了映射的狀態。 這意味着您必須使用適當的鎖定機制(同步部分、ReadWriteLock 等)來使用 HashMap。 並且,作為鎖定的結果,您可以實現您所需要的 - 讀者總是可以看到作者產生的所有更改,因為這些鎖會產生內存障礙/發生在之前的邊緣。
不,你沒有。
volatile
意味着變量不能緩存在寄存器中,因此將始終“直寫”到內存。 這意味着一個線程對變量的更改將對其他線程可見。
在這種情況下,變量是對 Map 的引用。 您始終使用相同的 Map,因此您不會更改引用 - 而是更改該 Map 的內容。 (也就是說, Map 是可變的。)這也意味着您可以,因此應該final
引用 Map 。
ConcurrentHashMap 與 HashMap 的不同之處在於,您通常可以安全地從不同線程同時讀取和寫入,無需外部鎖定。 但是,如果您希望能夠在任何給定點信任大小,執行先檢查后寫操作等,則需要自己設計。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.