ThreadLocal 源碼解讀

来源:https://www.cnblogs.com/jmcui/archive/2019/12/08/11994720.html
-Advertisement-
Play Games

一、引入 首先我們看到的是 Thread 中有一個屬性 threadLocals,它的類型是 ThreadLocalMap,封裝類型是 default(表示它只能在包內可見),jdk 是這麼介紹它的:與此線程有關的 ThreadLocal 值,該映射由 ThreadLocal 類維護。 啥意思呢?那 ...


一、引入

public class Thread implements Runnable {
    /* 前面略  */

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    /* 後面略  */
 }

首先我們看到的是 Thread 中有一個屬性 threadLocals,它的類型是 ThreadLocalMap,封裝類型是 default(表示它只能在包內可見),jdk 是這麼介紹它的:與此線程有關的 ThreadLocal 值,該映射由 ThreadLocal 類維護。 啥意思呢?那就來看看 ThreadLocalMap 是啥玩意!

public class ThreadLocal<T> {
   /* 前面略  */

    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
   /* 後面略  */
   }
}

從類定義上可以看出 ThreadLocal 是支持泛型的,而 ThreadLocalMap 是 ThreadLocal 的一個內部類,封裝類型也是 default(表示只能在包內可見),jdk 是這麼介紹它的:ThreadLocalMap 是自定義的哈希映射,僅適用於維護線程局部值。並且為了存儲容量可控,不至於記憶體泄漏,哈希表條目使用弱引用作為鍵(弱引用的對象的生命周期直到下一次垃圾回收之前被回收),ThreadLocalMap 使用靜態內部類 Entry(可以類比 Map 中的 entry)來存儲實際的 key 和 value。

從上面這些介紹,我們可以大致想到,ThreadLocal 是一個與線程相關的類,用來存儲維護線程局部值。

二、set(T value) 方法解讀

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

可以看到 set(T value) 方法做的事情很簡單,就是維護 Thread 的 threadLocals 屬性,如果該屬性不存在的話,就以當前 ThreadLocal 實例為 key 創建一個;該屬性存在的話,則直接賦值。

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

get() 的方法同樣很簡單,就是從 Thread 的 threadLocals 屬性獲取值,如果獲取不到,則把 initialValue() 的值賦值給線程的 threadLocals 屬性並返回。initialValue() 方法是一個 protected 類型的方法,預設返回 null,我們可以在創建 ThreadLocal 的時候重寫它,表示所有線程的預設值。

    // java8 的方式
    ThreadLocal<Boolean> threadLocal1 = ThreadLocal.withInitial(() -> false);
    // 
    ThreadLocal<Boolean> threadLocal2 = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

四、總結

  • ThreadLocal 用於存儲維護線程的局部值。
  • 和 ThreadLocal 類似的還有一個叫 InheritableThreadLocal, InheritableThreadLocal 繼承自 ThreadLocal,用於父子線程間共用共同的值,父線程中設置的值,子線程中可以訪問到。
  • 上面看 ThreadLocalMap 的時候,我們知道 key 是弱引用,gc 的時候 key 會被回收,但是 value 和 ThreadLocalMap 的引用不會被回收,如果這種情況的 Thread 很多,而且一直沒有執行完,就可能會出現記憶體泄漏,因此使用完 ThreadLocal 的時候儘量調用 ThreadLocal 的 remove() 方法。
  • 當使用線程池的時候,在調用 ThreadLocal 的 set 方法後,卻沒有調用 remove 的方法,如果同一個線程再去調用 get 方法可能拿到的值並不是當時 set 進去的(因為線程池的線程是復用的),可能導致程式數據異常之類的,因此使用完 ThreadLocal 的時候儘量調用 ThreadLocal 的 remove() 方法。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...