集合框架(13)-----ConcurrentHashMap源碼解析(jdk1.8)

基於JDK1.8

1. 原理解析

利用 ==CAS + synchronized== 來保證併發更新的安全
底層使用==數組+鏈表+紅黑樹==來實現
這裏寫圖片描述

1.1. 重要成員變量

  • table:默認爲null,初始化發生在第一次插入操作,默認大小爲16的數組,用來存儲Node節點數據,擴容時大小總是2的冪次方。
  • nextTable:默認爲null,擴容時新生成的數組,其大小爲原數組的兩倍。
  • sizeCtl :默認爲0,用來控制table的初始化和擴容操作,具體應用在後續會體現出來。
    -1 代表table正在初始化
    -N 表示有N-1個線程正在進行擴容操作
    其餘情況:
    1、如果table未初始化,表示table需要初始化的大小。
    2、如果table初始化完成,表示table的容量,默認是table大小的0.75倍,居然用這個公式算0.75(n - (n >>> 2))。
  • Node:保存key,value及key的hash值的數據結構。
    其中value和next都用volatile修飾,保證併發的可見性。
class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
    //... 省略部分代碼
}
  • ForwardingNode:一個特殊的Node節點,hash值爲-1,其中存儲nextTable的引用。
    只有table發生擴容的時候,ForwardingNode纔會發揮作用,作爲一個佔位符放在table中表示當前節點爲null或則已經被移動。
final class ForwardingNode<K,V> extends Node<K,V> {
    final Node<K,V>[] nextTable;
    ForwardingNode(Node<K,V>[] tab) {
        super(MOVED, null, null, null);
        this.nextTable = tab;
    }
}

ForwardingNodes are placed at the heads of bins during resizing. The types ForwardingNode does not hold normal user keys, values, or hashes, and are readily distinguishable during search etc because they have negative hash fields and null key and value fields.

1.2. 實例初始化

實例化ConcurrentHashMap時倘若聲明瞭table的容量,在初始化時會根據參數調整table大小,==確保table的大小總是2的冪次方==。默認的table大小爲16.

table的初始化操作回延緩到第一put操作再進行,並且初始化只會執行一次。

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
//如果一個線程發現sizeCtl<0,意味着另外的線程執行CAS操作成功,當前線程只需要讓出cpu時間片
        if ((sc = sizeCtl) < 0) 
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);  //0.75*capacity
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

Lazy table initialization minimizes footprint until first use, and also avoids resizings when the first operation is from a putAll, constructor with map argument, or deserialization. These cases attempt to override the initial capacity settings, but harmlessly fail to take effect in cases of races.
大白話:只有第一次使用才初始化,爲了防止初始化後的首次操作就需要擴容(比如putAll),從而影響效率。

1.3. put操作

1.3.1 put過程描述

假設table已經初始化完成,put操作採用==CAS+synchronized==實現併發插入或更新操作:
- 當前bucket爲空時,使用CAS操作,將Node放入對應的bucket中。
- 出現hash衝突,則採用synchronized關鍵字。倘若當前hash對應的節點是鏈表的頭節點,遍歷鏈表,若找到對應的node節點,則修改node節點的val,否則在鏈表末尾添加node節點;倘若當前節點是紅黑樹的根節點,在樹結構上遍歷元素,更新或增加節點。
- 倘若當前map正在擴容f.hash == MOVED, 則跟其他線程一起進行擴容

final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();  // lazy Initialization
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {  // 當前bucket爲空
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)  // 當前Map在擴容,先協助擴容,在更新值。
                tab = helpTransfer(tab, f); 
            else {  // hash衝突
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {  // 鏈表頭節點
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                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)  //鏈表節點超過了8,鏈表轉爲紅黑樹
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);  // 統計節點個數,檢查是否需要resize
        return null;
    }  

1.3.2 hash算法

與HashMap類似

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;}

1.3.3 定位索引

int index = (n - 1) & hash  // n爲bucket的個數

1.3.4 獲取table對應的索引元素f

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

採用Unsafe.getObjectVolatie()來獲取,而不是直接用table[index]的原因跟ConcurrentHashMap的弱一致性有關。在java內存模型中,我們已經知道每個線程都有一個工作內存,裏面存儲着table的副本,雖然table是volatile修飾的,但不能保證線程每次都拿到table中的最新元素,Unsafe.getObjectVolatile可以直接獲取指定內存的數據,保證了每次拿到數據都是最新的。

1.4. table 擴容

什麼時候會觸發擴容?
- 如果新增節點之後,所在的鏈表的元素個數大於等於8,則會調用treeifyBin把鏈表轉換爲紅黑樹。在轉換結構時,若tab的長度小於MIN_TREEIFY_CAPACITY,默認值爲64,則會將數組長度擴大到原來的兩倍,並觸發transfer,重新調整節點位置。(只有當tab.length >= 64, ConcurrentHashMap纔會使用紅黑樹。)
- 新增節點後,addCount統計tab中的節點個數大於閾值(sizeCtl),會觸發transfer,重新調整節點位置。

1.4.1 addCount

private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        // 利用CAS更新baseCount  
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            CounterCell a; long v; int m;
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                fullAddCount(x, uncontended); // 多線程修改baseCount時,競爭失敗的線程會執行fullAddCount(x, uncontended),把x的值插入到counterCell類中
                return;
            }
            if (check <= 1)
                return;
            s = sumCount();
        }
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                int rs = resizeStamp(n);
                if (sc < 0) {  
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)  // 其他線程在初始化,break;
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))  // 其他線程正在擴容,協助擴容
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,    
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);  // 僅當前線程在擴容
                s = sumCount();
            }
        }
    }

1.4.2 treeify

private final void treeifyBin(Node<K,V>[] tab, int index) {  
        Node<K,V> b; int n, sc;  
        if (tab != null) {  
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)//如果table.length<64 就擴大一倍 返回  
                tryPresize(n << 1);  
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {  
                synchronized (b) {  
                    if (tabAt(tab, index) == b) {  
                        TreeNode<K,V> hd = null, tl = null;  
                        //構造了一個TreeBin對象 把所有Node節點包裝成TreeNode放進去  
                        for (Node<K,V> e = b; e != null; e = e.next) {  
                            TreeNode<K,V> p =  
                                new TreeNode<K,V>(e.hash, e.key, e.val,  
                                                  null, null);//這裏只是利用了TreeNode封裝 而沒有利用TreeNode的next域和parent域  
                            if ((p.prev = tl) == null)  
                                hd = p;  
                            else  
                                tl.next = p;  
                            tl = p;  
                        }  
                        //在原來index的位置 用TreeBin替換掉原來的Node對象  
                        setTabAt(tab, index, new TreeBin<K,V>(hd));  
                    }  
                }  
            }  
        }  
    }  

1.4.3 transfer

當table的元素數量達到容量閾值sizeCtl,需要對table進行擴容:
- 構建一個nextTable,大小爲table兩倍
- 把table的數據複製到nextTable中。
在擴容過程中,依然支持併發更新操作;也支持併發插入。
這裏寫圖片描述

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];  // 構建一個nextTable,大小爲table兩倍
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        //通過for自循環處理每個槽位中的鏈表元素,默認advace爲真,通過CAS設置transferIndex屬性值,並初始化i和bound值,i指當前處理的槽位序號,bound指需要處理的槽位邊界,先處理槽位15的節點;
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) { // 遍歷table中的每一個節點 
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {  // //如果所有的節點都已經完成複製工作  就把nextTable賦值給table 清空臨時對象nextTable  
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);  //擴容閾值設置爲原來容量的1.5倍  依然相當於現在容量的0.75倍
                    return;
                }
                // 利用CAS方法更新這個擴容閾值,在這裏面sizectl值減一,說明新加入一個線程參與到擴容操作
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //如果遍歷到的節點爲空 則放入ForwardingNode指針 
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            //如果遍歷到ForwardingNode節點  說明這個點已經被處理過了 直接跳過  這裏是控制併發擴容的核心  
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {  
                        Node<K,V> ln, hn;
                        if (fh >= 0) {  // 鏈表節點
                            int runBit = fh & n;  // resize後的元素要麼在原地,要麼移動n位(n爲原capacity),詳解見:https://huanglei.rocks/coding/194.html#4%20resize()%E7%9A%84%E5%AE%9E%E7%8E%B0
                            Node<K,V> lastRun = f;
                            //以下的部分在完成的工作是構造兩個鏈表  一個是原鏈表  另一個是原鏈表的反序排列
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            //在nextTable的i位置上插入一個鏈表 
                            setTabAt(nextTab, i, ln);
                            //在nextTable的i+n的位置上插入另一個鏈表
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            //設置advance爲true 返回到上面的while循環中 就可以執行i--操作

                            advance = true;
                        }
                        //對TreeBin對象進行處理  與上面的過程類似 
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            //構造正序和反序兩個鏈表 
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            // (1)如果lo鏈表的元素個數小於等於UNTREEIFY_THRESHOLD,默認爲6,則通過untreeify方法把樹節點鏈表轉化成普通節點鏈表;(2)否則判斷hi鏈表中的元素個數是否等於0:如果等於0,表示lo鏈表中包含了所有原始節點,則設置原始紅黑樹給ln,否則根據lo鏈表重新構造紅黑樹。
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd); // tab[i]已經處理完了
                            advance = true;
                        }
                    }
                }
            }
        }
    }

關於ln,hn的操作詳見:https://www.jianshu.com/p/f6730d5784ad
關於紅黑樹的構建過程詳見:https://www.jianshu.com/p/23b84ba9a498

如何在擴容時,併發地複製與插入?
1. 遍歷整個table,當前節點爲空,則採用CAS的方式在當前位置放入fwd
2. 當前節點已經爲fwd(with hash field “MOVED”),則已經有有線程處理完了了,直接跳過 ,這裏是控制併發擴容的核心
3. 當前節點爲鏈表節點或紅黑樹,重新計算鏈表節點的hash值,移動到nextTable相應的位置(構建了一個反序鏈表和順序鏈表,分別放置在i和i+n的位置上)。移動完成後,用Unsafe.putObjectVolatile在tab的原位置賦爲爲fwd, 表示當前節點已經完成擴容。

==此處遺留一個問題:紅黑樹在擴容時是如何分別構建正序與反序鏈表的?==

1.5. get操作

讀取操作,不需要同步控制,比較簡單
1. 空tab,直接返回null
2. 計算hash值,找到相應的bucket位置,爲node節點直接返回,否則返回null

public V get(Object key) {
  Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        else if (eh < 0)
            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;
}

1.6. 統計size

ConcurrentHashMap的元素個數等於baseCounter和數組裏每個CounterCell的值之和,這樣做的原因是,當多個線程同時執行CAS修改baseCount值,失敗的線程會將值放到CounterCell中。所以統計元素個數時,要把baseCount和counterCells數組都考慮。、

    /**
     * Base counter value, used mainly when there is no contention,
     * but also as a fallback during table initialization
     * races. Updated via CAS.
     */
    private transient volatile long baseCount;

    /**
     * Table of counter cells. When non-null, size is a power of 2.
     */
    private transient volatile CounterCell[] counterCells;
@sun.misc.Contended static final class CounterCell {
        volatile long value;
        CounterCell(long x) { value = x; }
    }
    /**
     * Returns the number of mappings. This method should be used
     * instead of {@link #size} because a ConcurrentHashMap may
     * contain more mappings than can be represented as an int. The
     * value returned is an estimate; the actual count may differ if
     * there are concurrent insertions or removals.
     *(大致的意思是:返回容器的大小。這個方法應該被用來代替size()方法,因爲
     * ConcurrentHashMap的容量大小可能會大於int的最大值。
     * 返回的值是一個估計值;如果有併發插入或者刪除操作,則實際的數量可能有所不同。)
     * @return the number of mappings
     * @since 1.8
     */

final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

public int size() {
        long n = sumCount();
        return ((n < 0L) ? 0 :
                (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                (int)n);
    }



public long mappingCount() {
        long n = sumCount();
        return (n < 0L) ? 0L : n; // ignore transient negative values
    }

1.7 刪除元素

1.7.1 清空map:clear

清空tab的過程:
遍歷tab中每一個bucket,
1. 當前bucket正在擴容,先協助擴容
2. 給當前bucket上鎖,刪除元素
3. 更新map的size

public void clear() { // 移除所有元素  
    long delta = 0L; // negative number of deletions  
    inti = 0;  
    Node<K,V>[] tab = table;  
    while (tab != null && i < tab.length) {  
       intfh;  
       Node<K,V> f = tabAt(tab, i);  
       if (f == null) // 爲空,直接跳過  
           ++i;  
       else if ((fh = f.hash) == MOVED) { //檢測到其他線程正對其擴容  
//則協助其擴容,然後重置計數器,重新挨個刪除元素,避免刪除了元素,其他線程又新增元素。  
           tab = helpTransfer(tab, f);  
           i = 0; // restart  
       }  
       else{  
           synchronized (f) { // 上鎖  
               if (tabAt(tab, i) == f) { // 其他線程沒有在此期間操作f  
                  Node<K,V> p = (fh >= 0 ? f :  
                               (finstanceof TreeBin) ?  
                               ((TreeBin<K,V>)f).first : null);  
                   while (p != null) { // 首先刪除鏈、樹的末尾元素,避免產生大量垃圾  
                       --delta;  
                       p = p.next;  
                   }  
                   setTabAt(tab, i++, null); // 利用CAS無鎖置null  
               }  
           }  
       }  
    }  
    if (delta != 0L)  
       addCount(delta, -1); // 無實際意義,參數check<=1,直接return。  
}  

1.7.2 刪除元素

/**
 * Removes the key (and its corresponding value) from this map.
 * This method does nothing if the key is not in the map.
 *
 * @param  key the key that needs to be removed
 * @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 is null
 */
public V remove(Object key) {
    return replaceNode(key, null, null);
}

/**
 * Implementation for the four public remove/replace methods:
 * Replaces node value with v, conditional upon match of cv if
 * non-null.  If resulting value is null, delete.
 */
final V replaceNode(Object key, V value, Object cv) {
    int hash = spread(key.hashCode());
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0 ||
            (f = tabAt(tab, i = (n - 1) & hash)) == null)
            break;  // 桶位爲空,跳過
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);  // 協助擴容 
        else {
            V oldVal = null;
            boolean validated = false;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        validated = true;
                        for (Node<K,V> e = f, pred = null;;) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                V ev = e.val;
                                if (cv == null || cv == ev ||
                                    (ev != null && cv.equals(ev))) {
                                    oldVal = ev;
                                    if (value != null)
                                        e.val = value;
                                    else if (pred != null)  // 非鏈表頭節點,直接刪除該節點
                                        pred.next = e.next;
                                    else  // 更新鏈表頭節點
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            pred = e;
                            if ((e = e.next) == null)
                                break;
                        }
                    }
                    else if (f instanceof TreeBin) {
                        validated = true;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> r, p;
                        if ((r = t.root) != null &&
                            (p = r.findTreeNode(hash, key, null)) != null) {
                            V pv = p.val;
                            if (cv == null || cv == pv ||
                                (pv != null && cv.equals(pv))) {
                                oldVal = pv;
                                if (value != null)
                                    p.val = value;
                                else if (t.removeTreeNode(p))  // 當紅黑樹太小,會返回true
                                    setTabAt(tab, i, untreeify(t.first));
                            }
                        }
                    }
                }
            }
            if (validated) {
                if (oldVal != null) {
                    if (value == null)
                        addCount(-1L, -1);
                    return oldVal;
                }
                break;
            }
        }
    }
    return null;
}

2. ConcurrentHashMap 在1.7與1.8中的不同

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

項目 JDK1.7 JDK1.8
概覽 這裏寫圖片描述 這裏寫圖片描述
同步機制 分段鎖,每個segment繼承ReentrantLock CAS + synchronized保證併發更新
存儲結構 數組+鏈表 數組+鏈表+紅黑樹
鍵值對 HashEntry Node
put操作 多個線程同時競爭獲取同一個segment鎖,獲取成功的線程更新map;失敗的線程嘗試多次獲取鎖仍未成功,則掛起線程,等待釋放鎖 訪問相應的bucket時,使用sychronizeded關鍵字,防止多個線程同時操作同一個bucket,如果該節點的hash不小於0,則遍歷鏈表更新節點或插入新節點;如果該節點是TreeBin類型的節點,說明是紅黑樹結構,則通過putTreeVal方法往紅黑樹中插入節點;更新了節點數量,還要考慮擴容和鏈表轉紅黑樹
size實現 統計每個Segment對象中的元素個數,然後進行累加,但是這種方式計算出來的結果並不一樣的準確的。先採用不加鎖的方式,連續計算元素的個數,最多計算3次:如果前後兩次計算結果相同,則說明計算出來的元素個數是準確的;如果前後兩次計算結果都不同,則給每個Segment進行加鎖,再計算一次元素的個數; 通過累加baseCount和CounterCell數組中的數量,即可得到元素的總個數;

3. ConcurrentHashMap與HashMap的區別

ConcurrentHashMap是HashMap的高併發版本

4. ConcurrentHashMap能完全替代HashTable嗎?

hash table雖然性能上不如ConcurrentHashMap,但並不能完全被取代,兩者的迭代器的一致性不同的,hash table的迭代器是強一致性的,而concurrenthashmap是弱一致的。 ConcurrentHashMap的get,clear,iterator 都是弱一致性的。
下面是大白話的解釋:
- Hashtable的任何操作都會把整個表鎖住,是阻塞的。好處是總能獲取最實時的更新,比如說線程A調用putAll寫入大量數據,期間線程B調用get,線程B就會被阻塞,直到線程A完成putAll,因此線程B肯定能獲取到線程A寫入的完整數據。壞處是所有調用都要排隊,效率較低。
- ConcurrentHashMap 是設計爲非阻塞的。在更新時會局部鎖住某部分數據,但不會把整個表都鎖住。同步讀取操作則是完全非阻塞的。好處是在保證合理的同步前提下,效率很高。壞處 是嚴格來說讀取操作不能保證反映最近的更新。例如線程A調用putAll寫入大量數據,期間線程B調用get,則只能get到目前爲止已經順利插入的部分 數據。

選擇哪一個,是在性能與數據一致性之間權衡。ConcurrentHashMap適用於追求性能的場景,大多數線程都只做insert/delete操作,對讀取數據的一致性要求較低。

ConcurrentHashMap與HashTable一致性檢測

5. 實戰

https://blog.csdn.net/gjt19910817/article/details/47353909

6. 紅黑樹的加鎖機制(待更新)

紅黑樹還在學~
讀寫鎖
更改操作(delete,update,insert)必須等ongoing readers to finish.
讀取操作不需要加鎖。

Reference

https://www.jianshu.com/p/c0642afe03e0
https://blog.csdn.net/u010723709/article/details/48007881
http://www.cnblogs.com/loren-Yang/p/7466111.html

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