1. 程式人生 > >ConcurrentHashMap原始碼解析(3)

ConcurrentHashMap原始碼解析(3)

此文已由作者趙計剛授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


4、get(Object key)

使用方法:

map.get("hello");

原始碼:

 ConcurrentHashMap的get(Object key)

    /**
     * 根據key獲取value
     * 步驟:
     * 1)根據key獲取hash值
     * 2)根據hash值找到相應的Segment
     * 呼叫Segment的get(Object key, int hash)
     * 3)根據hash值找出HashEntry陣列中的索引index,並返回HashEntry[index]
     * 4)遍歷整個HashEntry[index]連結串列,找出hash和key與給定引數相等的HashEntry,例如e,
     * 4.1)如沒找到e,返回null
     * 4.2)如找到e,獲取e.value
     * 4.2.1)如果e.value!=null,直接返回
     * 4.2.2)如果e.value==null,則先加鎖,等併發的put操作將value設定成功後,再返回value值
     */
    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }

Segment的get(Object key, int hash)

        /**
         * 根據key和hash值獲取value
         */
        V get(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K, V> e = getFirst(hash);//找到HashEntry[index]
                while (e != null) {//遍歷整個連結串列
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        /*
                         * 如果V等於null,有可能是當下的這個HashEntry剛剛被建立,value屬性還沒有設定成功,
                         * 這時候我們讀到是該HashEntry的value的預設值null,所以這裡加鎖,等待put結束後,返回value值
                         */
                        return readValueUnderLock(e); 
                    }
                    e = e.next;
                }
            }
            return null;
        }

Segment的getFirst(int hash)

        /**
         * 根據hash值找出HashEntry陣列中的索引index,並返回HashEntry[index]
         */
        HashEntry<K, V> getFirst(int hash) {
            HashEntry<K, V>[] tab = table;
            return tab[hash & (tab.length - 1)];
        }

Segment的readValueUnderLock(HashEntry<K, V> e)

        V readValueUnderLock(HashEntry<K, V> e) {
            lock();
            try {
                return e.value;
            } finally {
                unlock();
            }
        }

注意點:

 

  • 註釋很重要,一定要看

  • 註釋已經寫明瞭詳細流程,這裡說一下大致流程:

    • 如沒找到e,返回null

    • 如找到e,獲取e.value

    • 如果e.value!=null,直接返回

    • 如果e.value==null,則先加鎖,等併發的put操作將value設定成功後,再返回value值

    • 根據key獲取hash值

    • 根據hash值找到相應的Segment

    • 根據hash值找出Segment中的哪一個HashEntry[index]

    • 遍歷整個HashEntry[index]連結串列,找出hash和key與給定引數相等的HashEntry,例如e

  • 對於get操作而言,基本沒有鎖,只有當找到了e且e.value等於null,有可能是當下的這個HashEntry剛剛被建立,value屬性還沒有設定成功,這時候我們讀到是該HashEntry的value的預設值null,所以這裡加鎖,等待put結束後,返回value值

  • 據說,上邊這一點還沒有發生過

 

5、remove(Object key)

使用方法:

map.remove("hello");

原始碼:

ConcurrentHashMap的remove(Object key)

    /**
     * 刪除指定key的元素
     * 步驟:
     * 1)根據key獲取hash值
     * 2)根據hash值獲取Segment
     * 呼叫Segment的remove(Object key, int hash, Object value)
     * 1)count-1
     * 2)獲取將要刪除的元素所在的HashEntry[index]
     * 3)遍歷連結串列,
     * 3.1)若沒有hash和key都與指定引數相同的節點e,返回null
     * 3.2)若有e,刪除指定節點e,並將e之前的節點重新排序後,將排序後的最後一個節點的下一個節點指定為e的下一個節點
     * (很繞,不知道JDK為什麼這樣實現)
     */
    public V remove(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }

Segment的remove(Object key, int hash, Object value)

        V remove(Object key, int hash, Object value) {
            lock();
            try {
                int c = count - 1;//key-value對個數-1
                HashEntry<K, V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K, V> first = tab[index];//獲取將要刪除的元素所在的HashEntry[index]
                HashEntry<K, V> e = first;
                //從頭節點遍歷到最後,若未找到相關的HashEntry,e==null,否則,有
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue = null;
                if (e != null) {//將要刪除的節點e
                    V v = e.value;
                    if (value == null || value.equals(v)) {
                        oldValue = v;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        ++modCount;
                        HashEntry<K, V> newFirst = e.next;
                        /*
                         * 從頭結點遍歷到e節點,這裡將e節點刪除了,但是刪除節點e的前邊的節點會倒序
                         * eg.原本的順序:E3-->E2-->E1-->E0,刪除E1節點後的順序為:E2-->E3-->E0
                         * E1前的節點倒序排列了
                         */
                        for (HashEntry<K, V> p = first; p != e; p = p.next)
                            newFirst = new HashEntry<K, V>(p.key, p.hash, newFirst, p.value);
                        tab[index] = newFirst;
                        count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

注意:具體的實現方式看註釋,個人感覺比較繞,所以有興趣的朋友可以按照如下步驟實現了一遍:(實現的過程可以參照HashMap的remove(Object key))

  • 根據key獲取hash值

  • 根據hash值獲取Segment

  • 獲取將要刪除的元素所在的HashEntry[index]

  • 遍歷連結串列

    • 若沒有hash和key都與指定引數相同的節點e,返回null

    • 若有e,刪除指定節點e,並將e的前一個節點的next指向e的下一個節點,之後count-1

 

6、containsKey(Object key)

使用方法:

map.containsKey("hello")

原始碼:

 ConcurrentHashMap的containsKey(Object key)

    /**
     * 是否包含指定key的資料
     * 步驟:
     * 1)根據key計算hash值
     * 2)根據hash獲取相應的Segment
     * 呼叫Segment的containsKey(Object key, int hash)
     * 3)根據hash值找出HashEntry陣列中的索引index,並返回HashEntry[index]
     * 4)遍歷整個HashEntry[index]連結串列,找出hash和key與給定引數相等的HashEntry,例如e,
     * 4.1)如找到e,返回true
     * 4.2)如沒找到e,返回false
     */
    public boolean containsKey(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).containsKey(key, hash);
    }

Segment的containsKey(Object key, int hash)

        boolean containsKey(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K, V> e = getFirst(hash);
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key))
                        return true;
                    e = e.next;
                }
            }
            return false;
        }

說明:程式碼清晰簡單,流程步驟檢視註釋即可

 

7、keySet().iterator()

使用方法:

        Map<String, Object> map = new ConcurrentHashMap<String, Object>();
        map.put("hello3", "world2");
        map.put("hello2", "world");
        for(String key : map.keySet()){
            System.out.println(map.get(key));
        }

原始碼不寫了。

流程:

遍歷每個Segment中的HashEntry[],完成所有物件的讀取,不加鎖。

 

8、size()

原始碼:

    /**
     * 計算map中的key-value對總數
     * 步驟:
     * 1)遍歷所有段,計算總的count值sum,計算總的modCount值
     * 2)如果有資料的話(modCount!=0),再遍歷所有段一遍,計算總的count值check,在這期間只要有一個段的modCount發生了變化,就再重複如上動作兩次
     * 3)若三次後,還未成功,遍歷所有Segment,分別加鎖(即建立全域性鎖),然後計算,最後釋放所有鎖
     */
    public int size() {
        final Segment<K, V>[] segments = this.segments;
        long sum = 0;//總量
        long check = 0;//標誌位
        int[] mc = new int[segments.length];//存放每個段的modCount
        
        
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
            check = 0;
            sum = 0;//總的count值
            int mcsum = 0;//總的modCount值
            for (int i = 0; i < segments.length; ++i) {//遍歷所有段
                sum += segments[i].count;//計算總的count值
                mcsum += mc[i] = segments[i].modCount;//計算總的modCount值
            }
            if (mcsum != 0) {//有資料的話,再檢查一遍
                for (int i = 0; i < segments.length; ++i) {
                    check += segments[i].count;//計算總的count
                    if (mc[i] != segments[i].modCount) {//只要有一個段發生了變化(在遍歷期間發生了增刪變化)
                        check = -1; 
                        break;//跳出所有迴圈
                    }
                }
            }
            if (check == sum)//成功
                break;
        }
        if (check != sum) { //以上三次都為成功的話
            sum = 0;
            //每一個段全部加鎖(相當於加了一個全域性鎖)
            for (int i = 0; i < segments.length; ++i)
                segments[i].lock();
            //進行統計
            for (int i = 0; i < segments.length; ++i)
                sum += segments[i].count;
            //全部解鎖
            for (int i = 0; i < segments.length; ++i)
                segments[i].unlock();
        }
        if (sum > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        else
            return (int) sum;
    }

在不加鎖的情況下遍歷所有Segment,讀取每個Segment的count和modCount,並進行統計;

完畢後,再遍歷一遍所有Segment,比較modCount,是否發生了變化,若發生了變化,則再重複如上動作兩次;

若三次後,還未成功,遍歷所有Segment,分別加鎖(即建立全域性鎖),然後計算,最後釋放所有鎖。

注:以如上的方式,大部分情況下,不需要加鎖就可以獲取size()

 

總結:

  • 資料結構:一個指定個數的Segment陣列,陣列中的每一個元素Segment相當於一個HashTable

  • 加鎖情況(分段鎖):

    • put

    • get中找到了hash與key都與指定引數相同的HashEntry,但是value==null的情況

    • remove

    • size():三次嘗試後,還未成功,遍歷所有Segment,分別加鎖(即建立全域性鎖)

jdk1.8 concurrentHashMap的實現


免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 UWP平臺Taglib編譯(2)