【源碼閱讀】ThreadLocal

1、前言

Q:什麼是 ThreadLocalhtml

A:ThreadLocal 只是一個引用名稱, 其類型爲 ThreadLocalMap;ThreadLocal 故名思議線程本地,字面上推斷出它的做用就是 ThreadLocal 爲每一個線程提供了一種將可變數據經過變爲私有副本從而實現線程封閉的功能。java

Q:若是叫你本身試着實現一個 ThreadLocal,你該如何去寫代碼?算法

A:由於咱們要實現線程私有,因此咱們的 key 得標記是哪一個線程,value 就是線程本身須要保存的數據咯;綜上,因此咱們須要實現一個線程安全的 Map,key、value 如上所述。segmentfault

2、源碼閱讀

2.1 結構總覽

繼承結構 在這裏插入圖片描述 在這裏插入圖片描述數組

在這裏插入圖片描述

  • 每一個線程 Thread 內部都有個 ThreadLocalMap
  • ThreadLocalMap 集合內部使用 Entry 來存儲鍵值對,其中 key 爲ThreadLocal(弱引用),value 爲實際存儲的值。

2.2 ThreadLocal.set(T val)

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocal.ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
    }

邏輯以下:安全

  1. 獲取當前線程實例
  2. 根據當前線程,獲取其中的 ThreadLocalMap 對象 threadLocals map
  3. 若 map 存在,則將 threadLocal 做爲 key 並保存 value
  4. 假如 map 爲 null,則建立新 map 而後塞值保存

2.3 ThreadLocal.remove(T val)

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
     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;
              }
          }
      }
  1. 獲取當前線程的 ThreadLocalMap threadLocals
  2. 而後傳入 ThreadLocal 對象做爲 key,而後遍歷 Entry 數組,找到 key 而後進行 remove 操做

2.4 ThreadLocal.get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    
    protected T initialValue() {
        return null;
    }
  1. 獲取當前線程實例
  2. 根據當前線程,獲取其中的 ThreadLocalMap 對象 threadLocals map
  3. 若 map 存在,將 threadLocal 做 爲key 得到 Entry 對象
  4. 若是 Entry 對象不爲空,返回其中的 Value
  5. 若是 map 爲空或者 map 爲不空且 entry 爲空則設置初始值 null 而後返回

3、ThreadLocalMap 介紹

2.1 ThreadLocalMap 的成員變量、方法

ThreadLocalMap 總覽 在這裏插入圖片描述this

重要的成員變量url

static class ThreadLocalMap {
	
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
        /**
         * The number of entries in the table.
         */
        private int size = 0;
        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0
        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
		...
}

由上可得:.net

  • ThreadLocalMap 並無繼承 Map
  • ThreadLocalMap 維護了一個 Entry 數組,而且要求數組的大小必須爲 2 的冪,同時記錄表裏面 entry 的個數以及下一次須要擴容的閾值。
    • Entry 用來保存 K-V,Entry 中 key 只能是 ThreadLocal 對象,且 Entry 繼承自 WeakReference(弱引用,生命週期只能存活到下次 GC 前),但只有 Key 是弱引用類型,Value 並不是弱引用類型。
  • ThreadLocalMap 有兩個方法用於獲得上一個/下一個索引,這是由於 ThreadLocalMap 使用線性探測法來解決散列衝突,因此實際上 Entry[] 數組在程序邏輯上是做爲一個環形存在的。 在這裏插入圖片描述

構造方法線程

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        //  用firstKey的threadLocalHashCode與初始大小16-1取模獲得數組下標
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        // 初始化該節點
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        // 設定擴容閾值
        setThreshold(INITIAL_CAPACITY);
    }

    /**
     * Construct a new map including all Inheritable ThreadLocals
     * from given parent map. Called only by createInheritedMap.
     *
     * @param parentMap the map associated with parent thread.
     */
    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();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }

4、文末存疑及總結

Q:ThreadLocalMap 如何解決哈希衝突?

A:ThreadLocalMap 根據初始 key 的 hashcode 肯定元素在 table 數組中的位置,若是發現這個位置上已經有其它 key 值的元素被佔用時,會利用固定的算法尋找必定步長的下個位置,直至找到可以存放的位置,這就是所謂的線性探測。


Q:ThreadLocalMap 中 Entry 類的 key 爲何是弱引用?

A:方便 JVM 進行垃圾回收;ThreadLocalMap 存儲的數據的格式是Entry<ThreadLocal, T>,且 Java 中而引用傳遞的是對象的副本,那麼若是使用強引用,當原來 key 對象失效的時候,JVM 不會回收 map 裏面的 ThreadLocal,而若是 key 爲弱引用,那麼 GC 在掃描到該對象時,不管內存充足與否,都會回收該對象的內存。


Q:key 設置爲弱引用有什麼問題?

A:從共享資源的角度可看出 ThreadLocal 是用空間換取時間,好比 Synchronize 從某種意義上來講,則是利用時間換取了空間,它對全部線程只設置一份變量,而若是使用 ThreadLocal,那麼每一個線程均可以存儲多個 ThreadLocal,可是當線程執行結束之後,ThreadLocal 對象會被回收,但這些線程內部的 ThreadLocalMap 中 Entry 對象的 value 還不會被回收,那麼這將很容易致使堆內存溢出。

針對這一問題,解決方案有:手動調用 ThreadLcoalMap 中的 get()/set()/remove() 方法實現 key 的刪除,這些方法在清理爲 null 的 key 時,會順帶清理其 value

https://blog.csdn.net/weixin_42388901/article/details/96491793

http://www.javashuo.com/article/p-vygdzrkq-hd.html


csdn

簡書

博客園:

其餘:

思否:

相關文章
相關標籤/搜索