【Java】JDK源碼分析——ThreadLocal

一.概述

ThreadLocal用於線程之間的變量共享,每一個線程都有自己獨立的副本,用來存儲自己的變量。只要線程是活躍的,其對應的副本就是可以訪問的。當線程死亡,其副本會作爲垃圾被回收。
ThreadLocal.java中的相關代碼:

public class ThreadLocal<T> {}

1.泛型T爲存儲和獲取數據的類型。

二.源碼分析

1.全局變量

ThreadLocal.java中的相關代碼:

    // ThreadLoacal的哈希值,用來區分不同線程的ThreadLocal
	private final int threadLocalHashCode = nextHashCode();

	// 用於計算哈希值,採用AtomicInteger保證併發時安全,初始值爲0
	private static AtomicInteger nextHashCode = new AtomicInteger();

	// 用於計算哈希值
	private static final int HASH_INCREMENT = 0x61c88647;

2. nextHashCode方法

用於產生ThreadLocal的哈希值。
ThreadLocal.java中的相關代碼:

	private static int nextHashCode() {
    	// 將nextHashCode的值加上HASH_INCREMENT的值,並返回
        return nextHashCode.getAndAdd(HASH_INCREMENT);
	}

3.構造方法

ThreadLocal.java中的相關代碼:

    public ThreadLocal() {
	}

4. initialValue方法

用於爲ThreadLocal提供當前線程的初始化值。
默認爲空,如果有需要,可以重寫該方法。
ThreadLocal.java中的相關代碼:

    protected T initialValue() {
        return null;
	}

5. getMap方法

獲取指定線程的ThreadLocalMap。
ThreadLocal.java中的相關代碼:

	ThreadLocalMap getMap(Thread t) {
    	// 返回線程的全局變量
        return t.threadLocals;
	}

6. createMap方法

爲指定線程創建ThreadLocalMap用於保存線程間共享的對象,並存儲firstValue。
ThreadLocal.java中的相關代碼:

	void createMap(Thread t, T firstValue) {
    	// 創建ThreadLocalMap對象
    	// 保存到線程的全局變量threadLocals中
        t.threadLocals = new ThreadLocalMap(this, firstValue);
	}

7. createInheritedMap方法

該方法用於根據父線程的ThreadLocalMap對象創建子線程繼承的ThreadLocalMap對象。
ThreadLocal.java中的相關代碼:

	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    	// 調用構造方法,創建新對象
        return new ThreadLocalMap(parentMap);
    }

8. childValue方法

該方法在InheritableThreadLocal類中被重寫,用於根據父值而給出子值。
該方法爲了在必要時繼承的子類可以不用重寫ThreadLocalMap類,而通過此方法實現原本的功能。
ThreadLocal.java中的相關代碼:

    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
	}

9. remove方法

刪除當前線程在ThreadLocal中存儲的值。
ThreadLocal.java中的相關代碼:

	public void remove() {
         // 獲取當前線程的ThreadLocalMap對象
         ThreadLocalMap m = getMap(Thread.currentThread());
         // 若ThreadLocalMap不爲空
         if (m != null)
             m.remove(this); // 移除當前線程存儲的值
     }

10. set方法

保存值到當前線程的ThreadLocalMap中。
ThreadLocal.java中的相關代碼:

	public void set(T value) {
    	// 獲取當前線程
        Thread t = Thread.currentThread();
        // 獲取當前線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        // 若ThreadLocalMap對象存在
        if (map != null)
            // 直接保存value
            map.set(this, value);
        else // 若ThreadLocalMap對象不存在
            // 則創建ThreadLocalMap對象,並保存value
            createMap(t, value);
    }

11. setInitialValue方法

用於將當前線程的ThreadLocalMap存儲的值設置爲初始值。
ThreadLocal.java中的相關代碼:

	private T setInitialValue() {
    	// 獲取初始值
        T value = initialValue();
        // 獲取當前線程
        Thread t = Thread.currentThread();
        // 獲取當前線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
		// 若ThreadLocalMap對象存在
        if (map != null)
            map.set(this, value); // 則保存值value
        else // 若ThreadLocalMap對象不存在
            createMap(t, value); // 則創建ThreadLocalMap對象,再保存值value
        // 返回初始值
        return value;
	}

12. get方法

獲取當前線程的ThreadLocalMap中對應的值。
ThreadLocal.java中的相關代碼:

	public T get() {
    	// 獲取當前線程
        Thread t = Thread.currentThread();
        // 獲取當前線程的ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        // 若ThreadLocalMap對象存在
        if (map != null) {
            // 嘗試獲取當前ThreadLocal對應的鍵值對
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 若鍵值對存在,說明之前存儲過
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 獲取鍵值對的值
                T result = (T)e.value;
                // 返回
                return result;
            }
        }
        // 若ThreadLocalMap對象不存在,
		// 則將ThreadLocal對應的值設置爲初始值,並返回
        return setInitialValue();
}

13.ThreadLocal中的靜態內部類ThreadLocalMap

ThreadLocalMap是ThreadLocal用於存儲鍵值對的內部類,ThreadLocal中對數據操作都是通過該內部類提供的方法實現的。
ThreadLocal.java中的相關代碼:

static class ThreadLocalMap {}

1)全局變量

ThreadLocal.java中的相關代碼:

		// 初始化容量,表示能夠存儲鍵值對的數量,必須是2的次方形式
        private static final int INITIAL_CAPACITY = 16;

        // 用於存儲鍵值對的數組
        private Entry[] table;

        // 用於記錄已經存儲的鍵值對的數量
        private int size = 0;

        // 擴容的閾值,存儲的鍵值對數量超過該值將會觸發擴容,默認爲0
        private int threshold;

2)ThreadLocalMap中的靜態內部類Entry

該類用於存儲鍵值對,其中鍵的類型爲ThreadLocal,值的類型爲Object。
同時,該類繼承了弱引用類WeakReference,當獲取鍵爲空時,說明該鍵的引用被回收。
ThreadLocal.java中的相關代碼:

		static class Entry extends WeakReference<ThreadLocal<?>> {
            // 線程中存儲的值
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

3)構造方法

i)參數爲鍵值對

該方法會創建ThreadLocalMap,並將參數中的鍵值對進行保存。
ThreadLocal.java中的相關代碼:

		ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 根據初始化容量,創建鍵值對數組
            table = new Entry[INITIAL_CAPACITY];
            // 獲取ThreadLocal的哈希值,和INITIAL_CAPACITY – 1進行與運算
            // 計算鍵值對在數組中的位置
            // INITIAL_CAPACITY – 1作爲掩碼
			// 如16-1=15,1111,最後進行與運算得到的數值一定在0到15之間
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // 創建新的鍵值對,保存到數組對應的位置中
            table[i] = new Entry(firstKey, firstValue);
            // 記錄當前鍵值對數量1
            size = 1;
            // 設置擴容閾值
            setThreshold(INITIAL_CAPACITY);
        }

ii)參數爲ThreadLocalMap

該方法用來根據繼承的ThreadLocalMap的內容創建新的ThreadLocalMap。
該方法是私有的,僅供createInheritedMap方法調用。
ThreadLocal.java中的相關代碼:

		private ThreadLocalMap(ThreadLocalMap parentMap) {
            // 獲取繼承的ThreadLocalMap中的鍵值對數組
            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];
                // 若j位置鍵值對存在
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    // 獲取鍵
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    // 若鍵存在
                    if (key != null) {
                        // 根據鍵,獲取值
                        Object value = key.childValue(e.value);
                        // 根據鍵和值,創建新的鍵值對
                        Entry c = new Entry(key, value);
                        // 根據鍵ThreadLocal的哈希值
						// 計算出鍵值對在數組中的位置h
						// len - 1作爲掩碼
						// 如16-1=15,1111,
						// 最後進行與運算得到的數值一定在0到15之間
                        int h = key.threadLocalHashCode & (len - 1);
                        // 循環,若數組的h位置有鍵值對
                        // 則根據當前位置h和數組長度,獲取下一個位置
                        // 直到找到一個沒有鍵值對的位置
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        // 將鍵值對存在對應位置
                        table[h] = c;
                        // 當前存儲的鍵值對數量加1
                        size++;
                    }
                }
            }
        }

4)setThreshold方法

用來設置擴容閾值。
ThreadLocal.java中的相關代碼:

		private void setThreshold(int len) {
            // 閾值爲數組長度的三分之二
            threshold = len * 2 / 3;
        }

5)nextIndex方法

獲取位置i的下一個位置。
ThreadLocal.java中的相關代碼:

		private static int nextIndex(int i, int len) {
            // 若i的下一個位置i+1小於數組長度len
            // 則返回i+1,否則返回0
            // 該操作相當於i對len取模
            return ((i + 1 < len) ? i + 1 : 0);
        }

6)prevIndex方法

獲取位置i的上一個位置。
ThreadLocal.java中的相關代碼:

		private static int prevIndex(int i, int len) {
            // 若i的上一個位置i-1大於0
            // 則返回i-1,否則返回len-1
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

7)expungeStaleEntry方法

刪除指定位置的鍵值對,並對其它位置的鍵值對重新散列整理。
ThreadLocal.java中的相關代碼:

		private int expungeStaleEntry(int staleSlot) {
            // 獲取數組
            Entry[] tab = table;
            // 獲取數組的長度
            int len = tab.length;

            // 刪除指定位置的鍵值對
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            // 存儲的鍵值對數量減1
            size--;

            // 對數組循環整理,重新散列,
			// 直到遇到爲空的位置,停止循環
            Entry e;
            int i;
            // 從staleSlot下一個位置i開始
            // 若i位置的鍵值對e爲空,則跳出循環
            // 每次循環i向後移動一個位置,若移動到最後一個位置,則回到0開始
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                // 從i位置的鍵值對e中獲取鍵k
                ThreadLocal<?> k = e.get();
                // 若鍵爲空
                if (k == null) {
                    // 清除當前位置的鍵值對
                    e.value = null;
                    tab[i] = null;
                    // 存儲的鍵值對數量減1
                    size--;
                } else { // 若鍵不爲空
                    // 重新計算鍵值對應該存放的位置h
                    int h = k.threadLocalHashCode & (len - 1);
                    // 若鍵值對應該存儲的位置和當前存放的位置不一致
                    if (h != i) {
                        // 對當前的位置進行清空
                        tab[i] = null;

                        // 循環,從鍵值對應該存放的位置h開始找
                        // 找到一個空位置,存放鍵值對
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            // 返回跳出循環時鍵值對爲空的位置
            return i;
        }

8)cleanSomeSlots方法

從位置i的下一位開始,向下查看log2(n)個位置,如果發現位置不對的鍵值對,則對其進行清理。
ThreadLocal.java中的相關代碼:

		private boolean cleanSomeSlots(int i, int n) {
            // 標誌位,如果進行過清理,則爲true
            boolean removed = false;
            // 獲取鍵值對數組
            Entry[] tab = table;
            // 獲取數組長度
            int len = tab.length;
            // 循環
            do {
                // 獲取位置i的下一個位置,結果再賦給i
                i = nextIndex(i, len);
                // 獲取該位置的鍵值對
                Entry e = tab[i];
                // 若該位置的鍵值對存在,並且鍵值對的鍵不存在
                if (e != null && e.get() == null) {
                    // 重新設置循環次數
                    n = len;
                    // 設置標誌位
                    removed = true;
                    // 刪除指定位置的鍵值對,並進行整理
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0); // 循環log2(n)次
            // 返回結果
            return removed;
        }

9)expungeStaleEntries方法

刪除數組中鍵不存在的鍵值對,對位置錯誤的鍵值對進行調整。
ThreadLocal.java中的相關代碼:

		private void expungeStaleEntries() {
            // 獲取鍵值對數組
            Entry[] tab = table;
            // 獲取數組長度
            int len = tab.length;
            // 循環遍歷鍵值對數組
            for (int j = 0; j < len; j++) {
                // 獲取鍵值對
                Entry e = tab[j];
                // 若鍵值對存在,並且鍵值對的鍵不存在
                if (e != null && e.get() == null)
                    // 刪除該位置的鍵值對,並整理。
                    expungeStaleEntry(j);
            }
        }

10)resize方法

用於對鍵值對數組擴容,擴大之前容量的2倍。
ThreadLocal.java中的相關代碼:

		private void resize() {
            // 獲取舊的鍵值對數組
            Entry[] oldTab = table;
            // 獲取舊的鍵值對數組的長度
            int oldLen = oldTab.length;
            // 根據舊長度計算新長度,新長度爲舊長度的2倍
            int newLen = oldLen * 2;
            // 根據新長度創建新數組
            Entry[] newTab = new Entry[newLen];
            // 用於記錄新數組使用的空間數量
            int count = 0;

            // 循環遍歷舊數組
            for (int j = 0; j < oldLen; ++j) {
                // 取出鍵值對
                Entry e = oldTab[j];
                // 若鍵值對存在
                if (e != null) {
                    // 獲取鍵值對的鍵
                    ThreadLocal<?> k = e.get();
                    // 若鍵不存在
                    if (k == null) {
                        // 則將其值刪除,即釋放引用
                        e.value = null; // 可以幫助垃圾回收器快點回收
                    } else { // 若鍵存在
                        // 重新計算其在新數組中的位置
                        int h = k.threadLocalHashCode & (newLen - 1);
                        // 若該位置已經被佔了,則循環查找下一個可以存放的位置
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        // 存放鍵值對到新數組中
                        newTab[h] = e;
                        // 新數組已經使用的空間數量加1
                        count++;
                    }
                }
            }

            // 設置新的擴容閾值
            setThreshold(newLen);
            // 將局部變量保存到全局變量
            // 將已經使用的空間數量保存到全局變量
            size = count;
            // 將新數組保存到全局變量
            table = newTab;
        }

11)rehash方法

對所有的鍵值對進行整理,刪除鍵不存在的鍵值對。
如果整理後鍵值對的數量還是過高,則進行擴容。
ThreadLocal.java中的相關代碼:

 		private void rehash() {
            // 整理所有鍵值對。
            expungeStaleEntries();

            // 當數組已經使用的空間數量超過閾值的四分之三
			// 即總空間的六分之一,則進行擴容
			// 使用更低的閾值避免滯後現象
            if (size >= threshold - threshold / 4)
                resize();
        }

12)replaceStaleEntry方法

該方法用於在set方法中將staleSlot位置處的鍵值對替換成鍵爲key值爲value的鍵值對。
ThreadLocal.java中的相關代碼:

		private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {

            // 獲取鍵值對數組
            Entry[] tab = table;
            // 獲取鍵值對數組的長度
            int len = tab.length;
            Entry e;

            // slotToExpunge用來標記需要進行刪除的位置
            int slotToExpunge = staleSlot;
            // 循環向前查找,找到一個鍵值對存在,
			// 但鍵值對中鍵不存在(被回收)的位置,直到遇到空的鍵值對結束循環
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i; // 用slotToExpunge標記該位置

            // 循環向後查找鍵值對,直到遇到空的鍵值對結束循環
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // 若鍵相等
                // 即本來要插入在staleSlot位置,但現在在i位置找到了對應的鍵
                if (k == key) {
                    // 修改鍵的值爲新值
                    e.value = value;

                    // 交換i位置和staleSlot位置的鍵值對
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // 若二者相等,說明之前循環向前查找沒有找到要找的鍵值對
                    if (slotToExpunge == staleSlot)
                        // 標記需要刪除的位置i,
						// i位置的鍵值對已經更新並轉移到staleSlot位置
                        slotToExpunge = i; 
                    // 刪除i位置的鍵值對,並對鍵值對數組進行整理 
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    // 返回
                    return;
                }

                // 若k爲空同時slotToExpunge和staleSlot相等
                // k爲空說明,當前的i位置是滿足鍵值對存在但鍵不存在的位置
                // slotToExpunge和staleSlot相等說明該位置之前向前查找時沒有找到
                if (k == null && slotToExpunge == staleSlot)
                    // 標記需要刪除的位置i
                    slotToExpunge = i;
            }

            // 代碼執行到此,說明循環中沒有找到存在的鍵值對,
			// 該鍵值對之前沒有存儲過
            // 刪除staleSlot位置的值(釋放引用,加速垃圾回收)
            tab[staleSlot].value = null;
            // 創建新鍵值對並存儲
            tab[staleSlot] = new Entry(key, value);

            // 若slotToExpunge和staleSlot不相等
            // 說明在循環向後查找中找到了需要刪除的鍵值對
            if (slotToExpunge != staleSlot)
                // 刪除slotToExpunge位置的鍵值對,並進行整理
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

13)remove方法

移除鍵爲key的鍵值對。
ThreadLocal.java中的相關代碼:

		private void remove(ThreadLocal<?> key) {
            // 獲取鍵值對數組
            Entry[] tab = table;
            // 獲取鍵值對數組長度
            int len = tab.length;
            // 計算鍵值對應該存儲的位置
            int i = key.threadLocalHashCode & (len-1);
            // 從i位置循環向後查找,直到遇到空鍵值對結束循環
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                // 若i位置的鍵值對的將和要移除的鍵值對的鍵相等
                if (e.get() == key) {
                    // 釋放鍵值對的引用(加速垃圾回收)
                    e.clear();
                    // 刪除位置i的鍵值對e
                    expungeStaleEntry(i);
                    // 返回
                    return;
                }
            }
        }

14)set方法

添加鍵值對。
ThreadLocal.java中的相關代碼:

		private void set(ThreadLocal<?> key, Object value) {

            // 獲取鍵值對數組
            Entry[] tab = table;
            // 獲取鍵值對數組長度
            int len = tab.length;
            // 計算鍵值對應該存儲的位置i
            int i = key.threadLocalHashCode & (len-1);

            // 循環從位置i向後查找,直到遇到空鍵值對跳出循環
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                // 獲取鍵值對的鍵
                ThreadLocal<?> k = e.get();

                // 若i位置鍵值對的鍵k和要添加的鍵值對的鍵key相等
                // 說明之前添加過
                if (k == key) {
                    // 更新鍵值對的值爲新的值value
                    e.value = value;
                    // 返回
                    return;
                }

                // 若當前位置i的鍵值對的鍵不存在(被回收)
                if (k == null) {
                    // 將新的鍵值對添加到位置i處
                    replaceStaleEntry(key, value, i);
                    // 返回
                    return;
                }
            }

            // 代碼執行到此處,說明該鍵值對之間沒有添加過
            // 並且也沒有合適的位置
            // 創建新的鍵值對,保存到位置i
            tab[i] = new Entry(key, value);
            // 當前存儲的鍵值對數量加1,並將其值賦給sz
            int sz = ++size;
            // 若整理空間失敗,同時當前存儲的鍵值對數量超過了擴容閾值
            // 說明當前存儲空間不足
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                // 重新散列,刪除鍵不存在的鍵值對,必要時將進行擴容
                rehash();
        }

三.總結

      1.ThreadLocal是用於在線程中存儲數據,其內部是通過ThreadLocalMap來存儲的,每一個線程都有一個對應ThreadLocalMap,ThreadLocalMap的內部維護了一個Entry數組。
      2.一個Entry就是一個鍵值對,該鍵值對鍵的類型爲ThreadLocal,值的類型爲Object。其中,由於Entry繼承了WeakReference,因此每個鍵值對Entry對鍵ThreadLocal的引用都是弱引用。
      3.每個ThreadLocal對象被創建時,都有不同的哈希值,該值會通過計算得到以該ThreadLocal爲鍵的鍵值對的存儲位置。當我們調用ThreadLocal的get和set方法,會將以該ThreadLocal爲鍵的鍵值對存儲到當前線程的ThreadLocalMap中。
      4.ThreadLocalMap存儲在線程中,但是對ThreadLocalMap的操作(如:創建、整理、刪除、添加、獲取…)須要通過ThreadLocal對象的方法來實現。
      5.ThreadLocalMap內部提供方法,會對鍵被回收的鍵值對進行清理,以保證空間存儲需要的鍵值對,當清理後的空間還是不足時,會觸發擴容。ThreadLocalMap的初始容量爲16,每次擴容會會擴大之前的2倍。

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