天天看點

Java7 HashMap和ConcurrentHashMap源碼閱讀

public class HashMap extends AbstractMap<K,V> impement Map<K,V>,Coloneable,Serializable{

可以看出HashMap實作了Map接口。其裡面的方法都是非線程安全的,且不支援并發操作。

對于HashMap主要看的是get/put方法實作,其在jdk1.7,及1.8在解決哈希沖突的上有所不同。

一、Java7 HashMap

Java7 HashMap和ConcurrentHashMap源碼閱讀

在HashMap中定義的成員變量:

capacity:目前數組容量,始終保持 2^n,可以擴容,擴容後數組大小為目前的 2 倍。

loadFactor:負載因子,預設為 0.75。

threshold:擴容的門檻值,等于 capacity * loadFactor,當容量超過這個值時,數組将擴容。

transient int modCount; //HashMap修改次數,這個值用于和expectedModCount期望修改次數比較。

1、put方法解析:

public V put(K key, V value) {

//1.當插入第一個元素時,需要建立并初始化指定大小的數組

if (table == EMPTY_TABLE) {

inflateTable(threshold);

}

二、java7 ConcurrentHashMap

在java7 下ConcurrentHashMap結構如下:

Java7 HashMap和ConcurrentHashMap源碼閱讀

現在我們已經說完了 put 過程和 get 過程,我們可以看到 get 過程中是沒有加鎖的,那自然我們就需要去考慮并發問題。

添加節點的操作 put 和删除節點的操作 remove 都是要加 segment 上的獨占鎖的,是以它們之間自然不會有問題,我們需要考慮的問題就是 get 的時候在同一個 segment 中發生了 put 或 remove 操作。

put 操作的線程安全性。

初始化槽,這個我們之前就說過了,使用了 CAS 來初始化 Segment 中的數組。

添加節點到連結清單的操作是插入到表頭的,是以,如果這個時候 get 操作在連結清單周遊的過程已經到了中間,是不會影響的。當然,另一個并發問題就是 get 操作在 put 之後,需要保證剛剛插入表頭的節點被讀取,這個依賴于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject。

擴容。擴容是新建立了數組,然後進行遷移資料,最後面将 newTable 設定給屬性 table。是以,如果 get 操作此時也在進行,那麼也沒關系,如果 get 先行,那麼就是在舊的 table 上做查詢操作;而 put 先行,那麼 put 操作的可見性保證就是 table 使用了 volatile 關鍵字。

remove 操作的線程安全性。

get 操作需要周遊連結清單,但是 remove 操作會”破壞”連結清單。

如果 remove 破壞的節點 get 操作已經過去了,那麼這裡不存在任何問題。

如果 remove 先破壞了一個節點,分兩種情況考慮。 1、如果此節點是頭結點,那麼需要将頭結點的 next 設定為數組該位置的元素,table 雖然使用了 volatile 修飾,但是 volatile 并不能提供數組内部操作的可見性保證,是以源碼中使用了 UNSAFE 來操作數組,請看方法 setEntryAt。2、如果要删除的節點不是頭結點,它會将要删除節點的後繼節點接到前驅節點中,這裡的并發保證就是 next 屬性是 volatile 的。

參考:https://javadoop.com/post/hashmap