簡體   English   中英

ConcurrentHashMap及其操作

[英]ConcurrentHashMap and its operations

假設有一個ConcurrentHashMap並且有兩個線程。

如果兩個線程都從同一個存儲桶中讀取某些數據,那么我的理解是,兩個線程都可以同時讀取該存儲桶,因為CHM不會阻止讀取操作。

但假設一個線程正在寫入( put )桶。 然后,第二個線程可以同時從同一存儲桶中讀取( get ),還是第二個線程必須等待put操作完成?

如果它是Hashtableget將不得不等到put操作完成。 但在CHM的情況下它會如何表現?

無需猜測。 ConcurrentHashMap源代碼已打開,任何人都可以閱讀。 (這是JDK 8內部版本128,這是第一個JDK 8發行候選版本。)

只需6300行,您就可以輕松理解它。 :-)實際上,其中很大一部分是注釋,大部分代碼都用於處理邊緣情況。 get()和put()的直接路徑並不是非常復雜,只有幾十行代碼。

您對讀取操作(get(),contains())的理解是正確的; 沒有阻擋。 如果需要,哈希到桶並在桶內搜索是直截了當的,沒有鎖定。 易失性讀取可確保內存可見性。 (在第622-623行, Nodevalnext字段是volatile。)讀操作與其他讀操作同時進行,並且同時寫入同一個存儲桶。

刪除和替換值的策略相當簡單,因為在搜索和修改存儲桶時,存儲桶的頭部被鎖定。 請參見replaceNode第1117行的synchronized塊。 添加到現有存儲桶中的put與此類似; 請參見putVal第1027行的synchronized塊。 這些操作當然會阻止其他線程嘗試刪除,替換或添加相同存儲桶的條目。 如果值正在被替換,則獲取此鍵值的線程將看到舊值或新值,具體取決於讀取線程是否在值被替換之前或之后找到節點寫線程。

將第一個元素放入存儲桶是一種特殊情況。 putVal行,如果putVal發現一個存儲桶為空,它將創建一個新的Node並將CAS(比較並交換)到位。 如果成功,則操作完成。 如果兩個線程嘗試或多或少同時將節點添加到同一存儲桶中,則第一個CAS將成功,而第二個CAS將失敗。 但是請注意,此代碼在for循環內(第1014行)。 CAS失敗的線程只是繞過循環並重試。 實際上,所有其他寫操作都在循環內。 通用方法是樂觀地進行操作,但會檢查並發寫入程序。 如果樂觀嘗試失敗,則重試該操作並基於現在更新的狀態經過(可能)不同的路徑。

據我所知,ConcurrentHashMap允許多個讀取器同時讀取而沒有任何阻塞。 這是通過基於並發級別將Map划分為不同的部分並在更新期間僅鎖定Map的一部分來實現的。 默認並發級別為16,因此Map分為16個部分,每個部分由不同的鎖控制。 這意味着,16個線程可以同時在Map上運行,直到它們在Map的不同部分上運行。 這使得ConcurrentHashMap在保持線程安全完整的情況下仍然具有高性能。 雖然,它帶有警告。 由於put(),remove(),putAll()或clear()等更新操作未同步,因此並發檢索可能無法反映Map上的最新更改。

我希望這個能幫上忙..

這來自ConcurrentHashMap類的JavaDocs:

“檢索操作(包括獲取)通常不會阻塞,因此可能與更新操作(包括放置和刪除)重疊。檢索反映了剛開始時最新完成的更新操作的結果”

在Hastable並發操作中將鎖定整個集合,但在ConcurrentHashMap中只會鎖定一個存儲桶。

來自doc:

哈希表支持檢索的完全並發性和可更新的可調整預期並發性。 該類遵循與Hashtable相同的功能規范,並包括與Hashtable的每個方法相對應的方法版本。 但是,即使所有操作都是線程安全的,檢索操作也不需要進行鎖定,並且不支持以阻止所有訪問的方式鎖定整個表。 在依賴於其線程安全性但不依賴於其同步詳細信息的程序中,此類可與Hashtable完全互操作。

檢索操作(包括get)通常不會阻塞,因此可能與更新操作(包括put和remove)重疊。 檢索反映了最近完成的更新操作的結果。 對於諸如putAll和clear之類的聚合操作,並發檢索可能反映僅插入或刪除某些條目。 類似地,迭代器和枚舉返回在創建迭代器/枚舉時或此后某個時刻反映哈希表狀態的元素。 它們不會拋出ConcurrentModificationException。 但是,迭代器被設計為一次只能由一個線程使用。

因此,您不應該期望操作完全像Hashtable一樣同步,但是相同(一系列)操作是線程安全的。 第二句強調並不意味着,但在我看來強烈建議,這是怎么回事就在這里:一個put正在進行中,即沒有完成,也不會阻止get -該get根本不會看到的變化呢。

雖然我沒有完成整個CHM課程,但這篇文檔支持我的假設(摘自OpenJDK 6)

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    /*
     * Segments maintain a table of entry lists that are always
     * kept in a consistent state, so can be read (via volatile
     * reads of segments and tables) without locking.  This
     * requires replicating nodes when necessary during table
     * resizing, so the old lists can be traversed by readers
     * still using old version of table.

當更新“完成”時,似乎沒有明確定義。 我想通常是將新存儲桶鏈接到存儲桶列表中。 CHM還大量使用易失性字段,以確保線程讀取列表中的最新存儲桶。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM