Java多線程——ConcurrentHashMap源碼解析

在之前討論HashMap與HashTable時提到過,HashMap沒有任何關於線程安全的處理,所以它不適合線程不安全的場景,而HashTable所有的操作方法都是加鎖的,所以它是線程安全的,但是由於HashTable的一些設計上的缺陷比如每一次put或者get操作都需要重新對hash值取模來計算它的位置所以效率低。我們可以在多線程環境下通過對調用HashMap的方法進行加鎖來確保其安全性,但是這樣子效率還是很低。以get來說,我們知道在多線程環境下get需要檢查集合中有沒有某個key值,它需要根據hash值計算出位置然後檢查對應的列表中有無尋找的元素,由於不能確定當前有沒有正在插入元素的線程,所以需要加鎖來保證安全性。但是,我們知道HashMap本身是箱式hash表,在下標位置不同時,兩個線程不會操作同一個鏈表,所以它們之前相互不影響,也就不存在衝突,可以不用搶佔同一個鎖。針對不同的箱有不同的鎖,這就是ConcurrentHashMap的設計方式——分段鎖。

ConcurrentMap也繼承了AbstractMap,實現了ConcurrentMap接口,key和value都不能爲null這點和HashMap是不同的,JDK1.8採用了Node+CAS+synchronized的設計取代了1.7中的Sgement+HashEntry的設計。並且沿用了HashMap的鏈表轉紅黑樹的設計。如果會修改集合中內容的方法發現正在進行resize操作,則要幫助先完成resize操作再繼續。

ConcurrentMap設計核心是在HashMap箱式鏈表的結構基礎上,每個線程只對一個箱上的結點加鎖,不同箱之間不存在線程衝突。對於初始化操作使用樂觀鎖策略,先新建對象,通過compareAndSwap策略修改值。CAS策略基於CPU指令的支持,比加鎖開銷要小很多,但是它在操作失敗時會自旋而不會阻塞讓出CPU時間片,導致如果設計合理線程間衝突小則性能很高,如果線程間衝突嚴重會顯著降低性能。

打開文件一看,都超過6k行了,所以還是採取從常用的方法入口開始逐漸向裏追溯的分析方法。主要分析構造函數以及get、put、remove、replace、size幾個常用的方法。

主要內部變量

這裏需要注意的主要是resize過程中,爲了多線程幫助轉移原有的鏈表,用nextTable作爲過程中的中間表。然後sizeCtl這個值正負有不同的意義,不再是capacity那樣只用來表示容量的。爲了避免插入新結點時計數器之前的碰撞,採用了分段式的計數器counterCells。

    /**
     * 箱數組,在第一次插入的時候懶漢式初始化。大小總是2的冪。迭代器可以直接訪問。
     */
    transient volatile Node<K,V>[] table;

    /**
     * 下一個使用的表,僅在resize過程中是非null
     */
    private transient volatile Node<K,V>[] nextTable;

    /**
     * 基本計數器,主要用於沒有爭奪時,但也用在表初始化爭奪中的返回,通過CAS更新
     */
    private transient volatile long baseCount;

    /**
     * 表初始化和resize控制。爲負數時,這個表正在初始化或者resize:-1是初始化,否則是-(1+活躍的resize線程)。
     * 未非負數時,表爲null時持有創建表時的初始化大小如果是0採用默認大小。初始化之後,持有下一個resize操作的計數界限。
     */
    private transient volatile int sizeCtl;

    /**
     * 在resize時下一個分裂處的下標
     */
    private transient volatile int transferIndex;

    /**
     * 通過CAS鎖定的自旋鎖,用於resize時或者創建CounterCells時
     */
    private transient volatile int cellsBusy;

    /**
     * 計數器存儲格表,非null時大小爲2的指數次
     */
    private transient volatile CounterCell[] counterCells;

構造函數

構造函數最多是3個參數,不輸入就採用默認值:表初始大小initialCapacity默認爲16;負載因子loadFactor默認爲0.75;併發級別concurrencyLevel代表線程數的估計值,默認爲1但除了初始大小不能小於這個值以外沒有其他作用。這裏表的初始大小也會取恰好大於等於參數中初始大小除以負載因子加1和的2的指數次冪作爲大小。sizeCtl爲下一次擴展集合的邊界,也就是容量。

    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        if (initialCapacity < concurrencyLevel)   // Use at least as many bins
            initialCapacity = concurrencyLevel;   // as estimated threads
        long size = (long)(1.0 + (long)initialCapacity / loadFactor);
        int cap = (size >= (long)MAXIMUM_CAPACITY) ?
            MAXIMUM_CAPACITY : tableSizeFor((int)size);//tableSizeFor獲取恰好大於等於size的2的指數次冪
        this.sizeCtl = cap;
    }

靜態工具方法

spread將hash值的高16位與低16位進行異或,作用是利用hash值的高位減少hash衝突,之前講過HashMap中直接通過截取與容量大小-1的二進制位長度來進行快速定位數組中的位置。>>>是無符號右移

    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

tableSizeFor計算大於等於給定值的最小2的指數次冪。我們知道二進制下低位全是1的數再加1可以獲得2的整數次冪,而這個全是1的數的獲取方法是,將原數-1後的值的最高位擴展到所有的低位,最後返回的結果再加1。

    private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

然後是三個CAS方法,都是基於Unsafe調用CPU支持的系統函數完成的

    //獲取tab中下標爲i的元素
    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);
    }
    //修改tab中下標爲i的元素值爲v,要求從內存中取出時的值爲c
    static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                        Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }
    //修改tab中下標爲i的元素的值爲v
    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

put操作

put方法有兩種類型put和putIfAbsent,可以看到它們都調用了putVal這個方法,區別在於第三個參數

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

    public V putIfAbsent(K key, V value) {
        return putVal(key, value, true);
    }

putVal就是實際操作了,首先檢查箱數組有沒有初始化,沒有的話先要初始化。然後根據hash值計算出箱的位置,如果箱的位置爲空則CAS新增一個結點。如果已有結點,則先檢查有沒有在做resize操作,有的話協助轉移Node,沒有在進行resize時對這個箱的結點加鎖,檢查箱式鏈表中有沒有key值相等的結點,有的話視onlyIfAbsent的參數情況決定要不要覆蓋。如果插入了新的結點到鏈表末尾需要檢查鏈表長度是否達到8需要轉爲樹結構,然後檢查是否達到要進行resize的大小。最後的返回值,如果key值已經存在則返回舊的value值,否則返回null。在第一行的檢查中我們可以看到,key和value都不能爲null,否則拋出NullPointerException

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();//插入的key和value都不能是null
        int hash = spread(key.hashCode());//高位也參與hash值計算
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {//tab爲箱數組
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();//在第一次插入時進行懶漢式初始化箱數組
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//tabAt從內存中讀取箱數組指定位置
                //箱數組指定位置爲null
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))//期待值n爲ull,更新後的值爲新建的Node,替換成功時返回true
                    break;                   //增加到一個空箱時不加鎖
            }
            //箱數組指定位置已有Node
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);//正在resize移動過程中,幫助轉移Node
            else {//存在普通狀態Node
                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)))) {//存在key相等的Node
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;//put調用時更新這個結點的value值
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);//不存在key相等的Node,將新Node添加到鏈表末尾
                                    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;//存在key相等的結點時返回該結點的value值
                    break;
                }
            }
        }
        addCount(1L, binCount);//添加了新的結點時增加基礎計數器,並檢查是否需要擴大數組
        return null;//不存在key相等的結點時返回null
    }

initTable初始化表,大小爲記錄在sizeCtl中的值,只有在第一次向集合中添加鍵值對時纔會調用。線程會嘗試將堆中實例的sizeCtl值設爲-1,如果成功的話代表該線程競爭到了初始化創建數組的權限,它將新建一個大小爲sizeCtl原本值的Node數組,並修改實例中的table,這裏的負載因子一定是按照0.75計算。最後將sizeCtl重置回原本的值。對於沒有競爭到的線程會通過Thread.yield()會主動讓出線程執行時間,線程轉爲就緒狀態,但這不代表它一定不會立刻再進入運行狀態。

    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // 在初始化競爭中失敗,自旋
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//嘗試替換sizeCtl的值爲-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,所以sizeCtl=n-n/4
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

addCount在新增結點時被調用,增加count,如果表太小並且還沒有resize,初始化轉移。如果已經resize,幫助轉移。重新檢查轉移後的佔用來看是否需要另一個resize,因爲resize比增加要延遲。如果check<0不檢查resize,如果check<=1只在沒有競爭時檢查resize。

    private final void addCount(long x, int check) {
        CounterCell[] as; long b, s;
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//counterCells存在或者當前增加baseCount存在競爭
            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);
                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) {//需要進行resize時循環
                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;//沒有在進行resize
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))//已有nextTab
                        transfer(tab, nt);//轉移tab中的內容到nextTable
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);//新建nextTable
                s = sumCount();
            }
        }
    }

fullAddCount作用是在在計數格不存在或者baseCount增加失敗時,嘗試初始化計數格或者循環嘗試增加baseCount。分析fullAddCount可以發現,計數器數組爲空時增加計數器數組大小爲2,而傳遞給addCount的check參數大小是鏈表長度。

    private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        if ((h = ThreadLocalRandom.getProbe()) == 0) {//檢查線程本地隨機數有沒有初始化
            ThreadLocalRandom.localInit();      // force initialization強制初始化
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty上一個槽非空時爲true
        for (;;) {
            CounterCell[] as; CounterCell a; int n; long v;
            if ((as = counterCells) != null && (n = as.length) > 0) {//counterCells不爲空
                if ((a = as[(n - 1) & h]) == null) {//當前線程對應的計數器槽爲空
                    if (cellsBusy == 0) {            // Try to attach new Cell嘗試關聯到一個新的計數格
                        CounterCell r = new CounterCell(x); // Optimistic create樂觀創建
                        if (cellsBusy == 0 &&
                            U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {//CAS算法嘗試替換cellsBusy值爲1
                            boolean created = false;
                            try {               // Recheck under lock在鎖下重新檢查
                                CounterCell[] rs; int m, j;
                                if ((rs = counterCells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;//賦值新建的計數格
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;//重置cellsBusy爲0
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty槽現在是非空
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail CAS已經知道失敗
                    wasUncontended = true;      // Continue after rehash再重新hash之後繼續
                else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                    break;//計數器增加成功則跳出
                else if (counterCells != as || n >= NCPU)
                    collide = false;            // At max size or stale最大大小或者已經過時
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 &&
                         U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {//計數格數組已經存在但不夠大,競爭擴展它的權限
                    try {
                        if (counterCells == as) {// Expand table unless stale除非已經過時否則擴展表
                            CounterCell[] rs = new CounterCell[n << 1];//新建一個大小爲2倍的數組
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];//逐個賦值複製計數格
                            counterCells = rs;//修改數組
                        }
                    } finally {
                        cellsBusy = 0;//重置cellsBusy
                    }
                    collide = false;
                    continue;                   // Retry with expanded table重新檢查擴展後的表
                }
                h = ThreadLocalRandom.advanceProbe(h);//僞隨機前進並記錄給定的探針值
            }
            else if (cellsBusy == 0 && counterCells == as &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {//counterCells爲空,競爭到初始化的權利
                boolean init = false;
                try {                           // Initialize table初始化表
                    if (counterCells == as) {
                        CounterCell[] rs = new CounterCell[2];//計數格表初始化大小爲2
                        rs[h & 1] = new CounterCell(x);//新建計數格
                        counterCells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;//初始化成功
            }
            else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
                break;                          // Fall back on using base使用base回退
        }
    }

transfer移動或者複製每個箱中的結點到新的表中,代碼很長,大概可以分爲幾個部分:1如果nextTab爲空新建一個;2每個線程分配到一個下標;3檢查這個下標是否有存在的結點並且沒有其他線程在修改它,有的話對它加鎖然後搬運。比較複雜的地方主要是分配下標,考慮到多線程進行操作時,每個箱同時只能由一個線程來處理,如果所有線程都採用從0到n的遍歷方式,衝突次數會大幅度上升,這裏採取的策略是通過transferIndex來記錄下一個線程進入時開始的下標位置進行分段,該值的變化間隔爲16或者n/8/CPU值的較大值。對一個箱的搬運工作參照HashMap的工作方式通過高位hash值將鏈表拆分到數組高位。

    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)//n/8/CPU個數<16
            stride = MIN_TRANSFER_STRIDE; // subdivide range細分範圍
        if (nextTab == null) {            // initiating初始化
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//新建一個當前表大小2倍的數組給nextTable
                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在提交nextTab前確保掃除完成
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                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))) {//減小transferIndex的值
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {//該線程負責轉移部分已經完成
                int sc;
                if (finishing) {//resize完成
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);//n*0.75
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {//增加一個活躍線程幫助resize
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit提交前重新檢查
                }
            }
            else if ((f = tabAt(tab, i)) == null)//表中不存在這個結點
                advance = casTabAt(tab, i, null, fwd);
            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;
                            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);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        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;
                                }
                            }
                            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);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

在putVal時,如果發現箱正在被轉移說明resize操作正在進行,調用helpTransfer幫助轉移

    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {//通過CAS將sizeCtl+1來競爭操作權
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

get

相比之下,get操作就比較簡單了,從堆中找到table中下標位置的結點,然後順着鏈表尋找key值相等的結點。find方法是預留給Node的子類重寫hash值小於0的方法

    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) {//從內存中獲取table指定下標位置的結點
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;//找到了指定的key值
            }
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;//留給子類的重寫方法,否則還是從這個鏈表中遍歷尋找
            while ((e = e.next) != null) {//遍歷鏈表尋找hash值
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

size

size返回鍵值對的數量,最大不能超過整數上限。

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

其中的關鍵是sumCount方法,我們前面提到有baseCount和counterCells數組兩個和計數有關的變量,分析addCount時我們可以看到優先增加baseCount的值,如果失敗的話找到線程對應的計數格增加它的計數器,所以鍵值對的個數爲兩類變量的總和。

    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;
    }

remove和replace

remove是移除指定的結點,replace是將指定結點替換爲指定值。雖然有不同的重載版本,區別是要不要指定結點的value值,但除了一些參數上的判斷之外,它們都是基於replaceNode方法來完成的。

    public V remove(Object key) {
        return replaceNode(key, null, null);
    }

    public boolean remove(Object key, Object value) {
        if (key == null)
            throw new NullPointerException();
        return value != null && replaceNode(key, null, value) != null;
    }

    public boolean replace(K key, V oldValue, V newValue) {
        if (key == null || oldValue == null || newValue == null)
            throw new NullPointerException();
        return replaceNode(key, newValue, oldValue) != null;
    }

    public V replace(K key, V value) {
        if (key == null || value == null)
            throw new NullPointerException();
        return replaceNode(key, value, null);
    }

replaceNode實現4個remove/replace操作:替換結點值爲v,如果cv不是null則它原本的value需要等於cv,如果結果value值是null則刪除這個結點。

    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;//表不存在或者表爲空或者表中找不到hash值對應的結點,直接返回
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);//正在resize先幫助轉移
            else {//表中存在該位置的結點
                V oldVal = null;
                boolean validated = false;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {//雙重確認
                        if (fh >= 0) {//hash值大於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;//value不爲null替換值
                                        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))
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                if (validated) {
                    if (oldVal != null) {
                        if (value == null)
                            addCount(-1L, -1);//減少計數器,因爲第二個參數是-1所以肯定不會檢查resize
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章