HashMap,HashTable,ConcurrentHashMap三者的聯繫與區別(有坑待填

HashMap,HashTable,ConcurrentHashMap三者的聯繫與區別


關於HashMap,前面已經研究過了HashMap的相關問題

但是HashMap是線程不安全的,所以接下來研究一下線程安全的HashTable,ConcurrentHashMap與HashMap的區別

區別

  1. HashMap允許插入null鍵和null值,但是HashTable和ConcurrentHashMap是不允許插入null鍵和null值的。

  2. 擴容機制:

    • HashTable 初始size爲11,擴容:newsize = olesize*2+1
    • HashMap 初始size爲16,擴容:newsize = oldsize*2
  3. 底層實現:

HashMap採用的是數組+鏈表+紅黑樹

HashTable採用的是數組+鏈表

ConcurrentHashMap採用分段的數組+鏈表實現

  1. 同步的實現:

    HashTable對每一個會修改集合的方法添加了synchronized,也就是每一次修改都是鎖住了整個對象,效率很低,基本被淘汰了,在JDK1.5,使用ConcurrentHashMap實現多線程下同步的功能。

一些面試問題

如何實現HashMap的線程安全

  • 使用Collections.synchronizedMap(Map)創建線程安全的map集合
  • Hashtable
  • ConcurrentHashMap

Collections.synchronizedMap(Map)的底層實現

private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }
    /*
    	public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        ...省略代碼
        */
}

可以看到,在代碼添加了互斥變量mutex,同時也提供兩種構造器,一種是默認的mutex,一種允許傳入自定義mutex。然後在這之後的代碼都都會對添加synchronized代碼塊以保證同步,也說明併發性能也不是很好。

快速失敗機制和安全失敗機制

在使用迭代器對集合對象進行遍歷的時候,如果 A 線程正在對集合進行遍歷,此時 B 線程對集合進行修改(增加、刪除、修改),那麼就會拋出ConcurrentModificationException 異常,這稱爲快速失敗機制。

在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷,這種操作稱爲安全失敗機制。

Synchronized鎖升級

img

首先需要知道一個東西,Java的對象頭,由Mark World ,指向類的指針,以及數組長度三部分組成,存儲在JVM堆中,用於保存對象的信息。其中有個叫Mark World 的,記錄了記錄了對象和鎖有關的信息,上圖是Mark World 的結構。

可以看到有4種鎖,按照鎖升級的順序排序,無鎖->偏向鎖 -> 輕量級鎖 -> 重量級鎖,消耗也是從左到右增加。

偏向鎖的引入

經過HotSpot的作者大量的研究發現,大多數時候是不存在鎖競爭的,常常是一個線程多次獲得同一個鎖,因此如果每次都要競爭鎖會增大很多沒有必要付出的代價,爲了降低獲取鎖的代價,才引入的偏向鎖。

偏向鎖的使用

這個流程網上看到了不同版本,也不知道哪個是真實的(霧),另外一個版本是說如果線程ID不同,通過CAS操作去獲得偏向鎖,如果失敗,就會升級鎖(可能就是下面2步中的簡化吧)

  • 當一個線程獲得一個對象鎖的時候,如果是無鎖狀態,那就設置偏向狀態,將當前進程ID記錄在Mark World中。

  • 如果已經設置了偏向狀態,查看Mark World中的線程ID是不是和當前線程ID一樣

    • 如果一樣,說明是同一個線程,直接訪問
    • 如果不一樣,說明不是一個線程,那就查看Mark World中的的標記線程是否還存活
      • 如果不存活,鎖對象就被重置爲無鎖狀態,然後當前線程可以獲得鎖,同樣設置偏向狀態和線程ID
      • 如果存活,立刻查找存儲線程的棧幀信息
        • 如果還是需要繼續持有這個鎖對象,那就暫停線程,升級爲輕量級鎖
        • 如果不需要,將鎖對象設位無鎖,設置爲偏向鎖

需要注意的幾點

  1. 偏向鎖不會主動釋放鎖,因爲偏向鎖主要是依靠線程ID相不相同去判斷的。

  2. 偏向鎖的撤銷需要等待全局安全點

輕量級鎖的引入

輕量級鎖考慮的是競爭鎖對象的線程不多,而且線程持有鎖的時間也不長的情景。因爲阻塞線程需要CPU從用戶態轉到內核態,代價較大,如果剛剛阻塞不久這個鎖就被釋放了,那這個代價就有點得不償失了,因此這個時候就乾脆不阻塞這個線程,讓它自旋這等待鎖釋放。

輕量級鎖的使用

  1. 線程由偏向鎖升級爲輕量級鎖時,會先把鎖的對象頭MarkWord複製一份到線程的棧幀中,建立一個名爲鎖記錄空間(Lock Record),用於存儲當前Mark Word的拷貝。
  2. 虛擬機使用cas操作嘗試將對象的Mark Word指向Lock Record的指針,並將Lock record裏的owner指針指對象的Mark Word。
  3. 如果cas操作成功,則該線程擁有了對象的輕量級鎖。第二個線程cas自選鎖等待鎖線程釋放鎖。
  4. 如果多個線程競爭鎖,輕量級鎖要膨脹爲重量級鎖,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針。其他等待線程進入阻塞狀態。

重量級鎖

自旋失敗,很大概率 再一次自選也是失敗,因此直接升級成重量級鎖,進行線程阻塞,減少cpu消耗。

當鎖升級爲重量級鎖後,未搶到鎖的線程都會被阻塞,進入阻塞隊列。

ConcurrentHashMap的jdk1.7實現

img

如圖所示,底層是由 Segment 數組、HashEntry 組成,和 HashMap1.7 一樣,仍然是數組加鏈表

能夠併發的原因

ConcurrentHashMap 採用了分段鎖技術,其中 Segment 繼承於 ReentrantLock。

不會像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理,理論上 ConcurrentHashMap 支持 CurrencyLevel (Segment 數組數量)的線程併發。

每當一個線程佔用鎖訪問一個 Segment 時,不會影響到其他的 Segment。

#### put操作

  1. 通過key定位到對應的Segment
  2. 嘗試自旋獲取鎖
  3. 當重試的次數達到了 MAX_SCAN_RETRIES 則改爲阻塞鎖獲取,保證能獲取成功。

get操作

1.Key 通過 Hash 之後定位到具體的 Segment

  1. 再通過一次 Hash 定位到具體的元素上
  2. 獲得值

ConcurrentHashMap的jdk1.8實現

能夠併發的原因

採用了 CAS + synchronized 來保證併發安全性。

put操作

  1. 通過key計算hashcode
  2. 判斷是否需要初始化
  3. 爲當前 key 定位出的 Node
    1. 如果爲空表示當前位置可以寫入數據,利用 CAS 嘗試寫入,失敗則自旋保證成功。
    2. 如果當前位置的 hashcode == MOVED == -1,則需要進行擴容。
    3. 如果都不滿足,則利用 synchronized 鎖寫入數據。
      1. 如果數量大於 TREEIFY_THRESHOLD 則要轉換爲紅黑樹。

get操作

  1. 兩次hash計算出hash值
  2. 檢查頭節點是否匹配
  3. 如果不行就檢查紅黑樹
  4. 還是不行就鏈表檢查

擴容機制//待補,很高深的樣子

參考資料

《我們一起進大廠》系列-ConcurrentHashMap & Hashtable

https://www.jianshu.com/p/e5ee7beadb45

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章