【併發容器精講1、】ConcurrentHashMap

1. 磨刀不誤砍柴功 :Map簡介

Map 是個接口 他會有許多實現以下:java

圖片在這裏插入圖片描述數組

  • HashMap安全

基本介紹:數據結構

1. 用於存儲Key-Value鍵值對的集合(每個鍵值對也叫作一個Entry)(無順序)。

2. 根據鍵的hashCode值存儲數據,大多數狀況下能夠直接定位到它的值。

3. 鍵key爲null的記錄至多隻容許一條,值value爲null的記錄能夠有多條。

4. 非線程安全。

5. HashMap是由數組+鏈表+紅黑樹(JDK1.8後增長了紅黑樹部分,鏈表長度超過閾值(8)時會將鏈表轉換爲紅黑樹)實現的。

6. HashMap與Hashtable區別:
        Hashtable是synchronized的。
        Hashtable不能夠接受爲null的鍵值(key)和值(value)。

簡單例子:併發

public static void main(String[] args) {
        Map<String,String>  map = new HashMap();
        map.put("1","1");
        System.out.println(map.isEmpty());
        System.out.println(map.keySet());
    }
JDK版本   實現方式    節點數>=8  節點數<=6
1.8之前   數組+單向鏈表  數組+單向鏈表    數組+單向鏈表
1.8之後   數組+單向鏈表+紅黑樹 數組+紅黑樹  數組+單向鏈表
  • Hastableapp

1. 和HashMap同樣,Hashtable 也是一個散列表,它存儲的內容是鍵值對(key-value)映射。
2. Hashtable 繼承於Dictionary,實現了Map、Cloneable、java.io.Serializable接口。
3. Hashtable 的函數都是同步的,這意味着它是線程安全的。它的key、value都不能夠爲null。此外,Hashtable中的映射不是有序的。

不少功能他和HashMap是一致的,可是他是線程安全的ide

  • LinkedHashMap函數

    基礎介紹源碼分析

1. LinkedHashMap是HashMap的一個子類,它保留插入的順序,若是須要輸出的順序和輸入時的相同,那麼就選用LinkedHashMap。

2. LinkedHashMap是Map接口的哈希表和連接列表實現,具備可預知的迭代順序。此實現提供全部可選的映射操做,並容許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。
3. LinkedHashMap實現與HashMap的不一樣之處在於,後者維護着一個運行於全部條目的雙重連接列表。此連接列表定義了迭代順序,該迭代順序能夠是插入順序或者是訪問順序。
   注意,此實現不是同步的。若是多個線程同時訪問連接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。
  • TreeMap性能

    基礎介紹

1. TreeMap 是一個有序的key-value集合,它是經過紅黑樹實現的。
2. TreeMap 繼承於AbstractMap,因此它是一個Map,即一個key-value集合。
3. TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。好比返回有序的key集合。
4. TreeMap 實現了Cloneable接口,意味着它能被克隆。
5. TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。

6. TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的天然順序進行排序,或者根據建立映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
7.  TreeMap的基本操做 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

2. 爲何須要ConcurrentHashMap

1.咱們先思考一個問題,咱們爲何要用ConcurrentHashMap,而不用Collections.synchronizedMap()  或者  Hashtable
:  加了鎖對咱們性能形成很大的影響

  1. 爲何HashMap是線程不安全的呢?
    :  同時put碰撞致使數據丟失
    :  同時put擴容致使數據丟失
    : 死循環形成CPU100%

3. 九層之臺,起於累土,羅馬不是一天建成的:HashMap分析

  • HashMap是應用更普遍的哈希表實現,並且大部分狀況下,都能在常數時間性能的狀況下進行put和get操做。要掌握HashMap,主要從以下幾點來把握:

  • jdk1.7中底層是由數組(也有叫作「位桶」的)+鏈表實現;jdk1.8中底層是由數組+鏈表/紅黑樹實現

  • 能夠存儲null鍵和null值,線程不安全 初始size爲16,擴容:newsize = oldsize*2,size必定爲2的n次冪

  • 擴容針對整個Map,每次擴容時,原來數組中的元素依次從新計算存放位置,並從新插入

  • 插入元素後才判斷該不應擴容,有可能無效擴容(插入後若是擴容,若是沒有再次插入,就會產生無效擴容)

  • 當Map中元素總數超過Entry數組的75%,觸發擴容操做,爲了減小鏈表長度,元素分配更均勻

1.7 實現圖

圖片在這裏插入圖片描述
1.8實現圖


圖片在這裏插入圖片描述

延伸:紅黑樹

x他是一個二叉查找數,一種平衡策略

圖片在這裏插入圖片描述


x左邊的值要比這個節點要小,右邊則大

x會自動平衡,防止極端不平衡從而影響查找效率的狀況發生

x每一個節點要們是紅色,要麼是黑色,但跟節點永遠是黑色

x紅色節點不能連續

x從任一節點到其子數中每一個葉子節點的路徑都包含相同數量的黑色節點

x全部的葉節點都是黑色的

4. JDK1.7 中 ConcurrentHashMap 實現和分析

1.7 數據結構

圖片在這裏插入圖片描述
1.7 的特色


  • Java 1.7 中ConcurrentHashMap最外層是多個segment,每一個segment底層數據結構與HashMap相似,ren仍然是數組+鏈表的 拉鍊法

  • 每一個segment獨立ReentrantLock,每一個segment 互不影響,提升鏈併發效率

  • ConcurrentHashMap 有16個 segment,因此同時支持 16個線程併發寫,這個默認值能夠在初始化的時候設置爲其餘值,可是一旦初始化後,是不能夠擴容的

5. JDK1.8 中 ConcurrentHashMap 實現和源碼分析

數據結構

圖片在這裏插入圖片描述
ConcurrentHashMap 借鑑鏈 1.8 HashMap 實現


源碼分析

  1. put

 /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <p>The value can be retrieved by calling the {@code get} method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}
     * @throws NullPointerException if the specified key or value is null
     */

    public V put(K key, V value) {
        return putVal(key, value, false);
    }

putVal 是 put 方法和核心

/** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 不容許key value 出現 空不然 拋出空指針異常
        if (key == null || value == nullthrow new NullPointerException();
        //計算hashcode值
        int hash = spread(key.hashCode());
        int binCount = 0;
        // 用for循環進行處理  完成值的插入工做
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //判斷 tab 是否初始化
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
                //已經被初始化 而且在這個位置是空的  執行cas操做直接放進去
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //cas操做把值放進去
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 判斷hash值是否  MOVED  MOVED表明擴容
            else if ((fh = f.hash) == MOVED)
            //幫助進行擴容
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //不然使用synchronized保證值線程的安全
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            //進行鏈表的操做
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //判斷當前是否存在key
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                //建立一個節點 放到鏈表的最後
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        //若是走到這裏 說明是一個 紅黑樹 進行紅黑樹的操做
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                // 判斷添加是否完成
                if (binCount != 0) {
                // 判斷鏈表 是否知足條件變成紅黑樹
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

圖片在這裏插入圖片描述

  1. get

/**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * <p>More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code key.equals(k)},
     * then this method returns {@code v}; otherwise it returns
     * {@code null}.  (There can be at most one such mapping.)
     *
     * @throws NullPointerException if the specified key is null
     */

    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //算出hash值
        int h = spread(key.hashCode());
        //判斷值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //若是槽點的hash值符合 返回val  找到值了
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 若是是負數,說明是紅黑樹節點,
            else if (eh < 0)
             //用find方法找到對應的value 
                return (p = e.find(h, key)) != null ? p.val : null;
                // 遍歷鏈表 找到值返回
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

圖片在這裏插入圖片描述

6. 對比1.7 與 1.8 ,爲何要把1.7的結構改爲1.8的結構

  1. 數據結構的區別

  • Java 1.7 中ConcurrentHashMap最外層是多個segment,每一個segment底層數據結構與HashMap相似 最多 16個

  • Java 1.8 使用 數組+鏈表+紅黑樹的結構  每一個 Node,提升了併發性

  1. hash碰撞

  • 轉變爲 數組 + 鏈表 +  紅黑樹

  1. 保證併發安全

  • java1.8 經過 cas + synchronized 實現

我的博客地址:http://blog.yxl520.cn/

相關文章
相關標籤/搜索