1. 程式人生 > >Java ThreadLocal原始碼解析: ThreadLocalMap

Java ThreadLocal原始碼解析: ThreadLocalMap

ThreadLocalMap在比其中Thread和ThreadLocal部分要複雜很多,是ThreadLocal底層儲存和核心資料結構。從整體上將,ThreadLocalMap底層是Entry陣列,key值為ThreadLocal的hash code, 採用線性探測法解決雜湊衝突。

以下是ThreadLocalMap核心屬性和方法,所有方法和屬性都標識為private,僅為ThreadLocal可以訪問:
在這裡插入圖片描述

ThreadLocalMap核心屬性分析,主要包括底層儲存資料結構,相關閾值計算,但負載因子計算貌似有點怪:

   /**
     * Entry[] table陣列的初始大小為16,這個數值必須為2的冪次方
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * 最終落地儲存的資料結構是陣列,其中Entry是個內部類,可以理解為key-value結構,
     * 這個陣列的長度必須是2的冪
     */
    private Entry[] table;

    /**
     * 陣列中實際佔用了的個數
     */
    private int size = 0;

    /**
     * 閾值,達到這個閾值,將對陣列進行擴容,因此需要進行rehash
     */
    private int threshold; // Default to 0

    /**
     * 這個方法就是設定threshold,是總長度的2/3
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    
    // 這些都是用來解決衝突的,說白了如果i這個位置被佔了,獲取i+1位置
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

以下為ThreadLocalMap的建構函式,分別是通過ThreadLocal和value值,和通過已有ThreadLocalMap構造:

        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {    // 實際上就是key value對
            table = new Entry[INITIAL_CAPACITY];   // 以初始化大小建立Entry陣列
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  // 生成hash值,這個值為啥要選這個值?
            table[i] = new Entry(firstKey, firstValue);   // 設定找到的位置的值
            size = 1;  // 大小為1
            setThreshold(INITIAL_CAPACITY);   // 更新擴容閾值
        }

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {          // 如果存在值
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();    // 獲取ThreadLocal
                    if (key != null) {      // 如果為空了就應該被回收掉
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);   // 獲取hash值
                        while (table[h] != null)    // 如果這個位置已經被佔用了,訪問下一個位置,直到不衝突,最終的效果是迴圈找
                            h = nextIndex(h, len);   // 當然是解決衝突
                        table[h] = c;   
                        size++;
                    }
                }
            }
        }

以下是ThreadLocalMap中核心操作方法,包括get,remove,rehash等基礎實現:

    private Entry getEntry(ThreadLocal<?> key) {    // 根據ThreadLocal獲取value值
        int i = key.threadLocalHashCode & (table.length - 1);  // 獲取訪問index
        Entry e = table[i];
        if (e != null && e.get() == key)  // 如果找到,並且驗證
            return e;
        else   // 否者執行反向的hash的過程,其實就是從這個位置一個一個往下找
            return getEntryAfterMiss(key, i, e);
    }

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {   // 迴圈從i位置往下找
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)  //找到了
                return e;
            if (k == null)  // 發現斷了,擦除失效的值
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);   //當前不匹配,找下一個值
            e = tab[i];
        }
        return null;
    }
    
   private int expungeStaleEntry(int staleSlot) {  // 擦除失效的節點
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;   // staleSlot這個位置失效了,置為空,size減一
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null 
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);   // 後面的邏輯就是從這個位置開始作rehash, 因為這個空了,如果後面有有效的,拿上來填上
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

    private void set(ThreadLocal<?> key, Object value) {   // 找到合適位置安放當前值,如果有失效的點,
   
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {     // 如果當前這個ThreadLocal已經有了,將新value替換之前的
                    e.value = value;
                    return;
                }

                if (k == null) {    // 如果找到了一個key為空的位置,這個位置失效了,替換掉這個失效的位置
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)   // 
                rehash();  // 大於threshold的0.75就重新hash
        }
        
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
     private void rehash() {
        expungeStaleEntries();

        if (size >= threshold - threshold / 4)    // 這裡有個數組的總長度,閾值,rehash的閾值,大概是總長度的2/3*0.75時才rehash
            resize();
    }

    /**
     * Double the capacity of the table.
     */
    private void resize() {
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        int newLen = oldLen * 2;  // 擴容2倍
        Entry[] newTab = new Entry[newLen];
        int count = 0;

        for (int j = 0; j < oldLen; ++j) {   // 重新hash
            Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null; // Help the GC,失效的值
                } else {
                    int h = k.threadLocalHashCode & (newLen - 1);
                    while (newTab[h] != null)    // 重新找到合適的位置
                        h = nextIndex(h, newLen);
                    newTab[h] = e;
                    count++;
                }
            }
        }

        setThreshold(newLen);
        size = count;
        table = newTab;
    }    

總結:ThreadLocalMap的實現也不是很複雜,底層為一個數組,通過ThreadLocal的threadLocalHashCode作雜湊,如果發現有實效的,觸發清除失效值,達到閾值觸發rehash,使用線性探測法定位hash位置,即通過hash值獲取陣列中的一個index位置,如果已被佔用就去下一個位置,如果發現失效的,觸發清除失效值。但是這裡的負載因子有點怪,看起來需要經過兩次計算,是2/3*0.75,及0.5