深入分析 ThreadLocal 的實現原理

      網友投稿 899 2022-05-30

      ThreadLocal主要是提供線程內部的局部變量,在每個線程內隨時隨地可取,隔離其他線程。

      1. ThreadLocal接口

      1.1 ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:

      void set(Object value)設置當前線程的線程局部變量的值。

      public Object get()該方法返回當前線程所對應的線程局部變量。

      public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。

      protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

      在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。

      而ThreadLocal則從另一個角度來解決多線程的并發訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。

      如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。最常見的ThreadLocal使用場景為 用來解決 數據庫連接、Session管理等。

      1.2 使用ThreadLocal

      public class ThreadTest { ThreadLocal longLocal = new ThreadLocal(){ protected Long initialValue() { return Thread.currentThread().getId(); } }; ThreadLocal stringLocal = new ThreadLocal(){; protected String initialValue() { return Thread.currentThread().getName(); } }; public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final ThreadTest test = new ThreadTest(); //test.set(); System.out.println("main.getLong: " + test.getLong()); System.out.println("main.getString: " + test.getString()); Thread thread1 = new Thread() { public void run() { //test.set(); System.out.println("Thread.getLong: " + test.getLong()); System.out.println("Thread.getString: " + test.getString()); } }; thread1.start(); thread1.join(); } }

      以上demo覆寫了initialValue()方法,或者調用set方法,否則會報空指針異常。在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。

      2. ThreadLocalMap

      ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。

      1. 實際的通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中的;

      2. 為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象,因為每個線程中可有多個threadLocal變量,就像上面代碼中的longLocal和stringLocal;

      3. 在進行get之前,必須先set,否則會報空指針異常;

      2.1 方法分析

      1). JDK8的ThreadLocal的get方法的源碼

      /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); }

      2). getMap

      /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }

      3). setInitialValue

      /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }

      get方法的流程是這樣的:

      1.首先獲取當前線程

      2.根據當前線程獲取一個Map

      3.如果獲取的Map不為空,則在Map中以ThreadLocal的引用作為key來在Map中獲取對應的value e,否則轉到5

      4.如果e不為null,則返回e.value,否則轉到5

      5.Map為空或者e為空,則通過initialValue函數獲取初始值value,然后用ThreadLocal的引用和value作為firstKey和firstValue創建一個新的Map

      每個Thread維護一個ThreadLocalMap映射表,這個映射表的key是ThreadLocal實例本身,value是真正需要存儲的Object。

      3. WeakReference

      關于內存泄露:

      ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:

      ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value

      主要看下getEntryAfterMiss函數:

      private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { 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; }

      ThreadLocalMap的getEntry函數的流程:

      首先從ThreadLocal的直接索引位置(通過ThreadLocal.threadLocalHashCode & (len-1)運算得到)獲取Entry e,如果e不為null并且key相同則返回e;

      如果e為null或者key不一致則向下一個位置查詢,如果下一個位置的key和當前需要查詢的key相等,則返回對應的Entry,否則,如果key值為null,則擦除該位置的Entry,否則繼續向下一個位置查詢

      在這個過程中遇到的key為null的Entry都會被擦除,那么Entry內的value也就沒有強引用鏈,自然會被回收。仔細研究代碼可以發現,set操作也有類似的思想,將key為null的這些Entry都刪除,防止內存泄露。 但是光這樣還是不夠的,上面的設計思路依賴一個前提條件:要調用ThreadLocalMap的genEntry函數或者set函數。這當然是不可能任何情況都成立的,所以很多情況下需要使用者手動調用ThreadLocal的remove函數,手動刪除不再需要的ThreadLocal,防止內存泄露。所以JDK建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據ThreadLocal的弱引用訪問到Entry的value值,然后remove它,防止內存泄露。

      深入分析 ThreadLocal 的實現原理

      參考:

      ThreadLocal和synchronized的區別

      Java并發編程:深入剖析ThreadLocal

      Java 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:【重大消息】智簡網絡社區內容“更新”啦!!!
      下一篇:數據安全保護,八招致勝
      相關文章
      亚洲人成网站在线观看播放动漫| 亚洲天堂中文资源| 亚洲综合精品香蕉久久网97| 亚洲日本va在线视频观看| 亚洲人成无码网WWW| 亚洲A丁香五香天堂网| 亚洲日韩在线中文字幕综合| 亚洲成在人线aⅴ免费毛片 | 亚洲色婷婷一区二区三区| 亚洲最大av无码网址| 狠狠色婷婷狠狠狠亚洲综合| 狠狠综合久久综合88亚洲| 国产亚洲一区区二区在线| 久久精品亚洲男人的天堂| 综合亚洲伊人午夜网| 亚洲人成人网站色www| 久久精品国产亚洲麻豆| 久久精品国产亚洲| 亚洲今日精彩视频| 亚洲av成人无码久久精品| 亚洲天堂一区二区| 亚洲理论在线观看| 亚洲一区二区三区在线| 亚洲熟妇av午夜无码不卡| 亚洲精品日韩一区二区小说| 337p日本欧洲亚洲大胆人人| 亚洲AV无码乱码在线观看牲色| 国产精品xxxx国产喷水亚洲国产精品无码久久一区| 深夜国产福利99亚洲视频| 亚洲天堂中文字幕在线| 亚洲国产一成人久久精品| 亚洲成色在线综合网站| 久久久亚洲欧洲日产国码二区| 亚洲色欲或者高潮影院| 亚洲国产成人99精品激情在线| 亚洲欧洲无码一区二区三区| 国产天堂亚洲国产碰碰| 久久精品夜色噜噜亚洲A∨| 亚洲AV午夜成人影院老师机影院| 久久精品国产亚洲AV无码麻豆 | 国产啪亚洲国产精品无码|