首先ThreadLocal
類是一個線程數據綁定類, 有點相似於HashMap<Thread, 你的數據>
(但實際上並不是如此), 它全部線程共享, 但讀取其中數據時又只能是獲取線程本身的數據, 寫入也只能給線程本身的數據java
public class ThreadLocalDemo { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { threadLocal.set("zhazha" + Thread.currentThread().getName()); String s = threadLocal.get(); System.out.println("threadName = " + Thread.currentThread().getName() + " [ threadLocal = " + threadLocal + "\t data = " + s + " ]"); }, "threadName" + i).start(); } } }
從他的輸入來看, ThreadLocal
是同一個, 數據存的是線程本身的名字, 因此和threadName
是同樣的名稱shell
threadName = threadName9 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName9 ] threadName = threadName3 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName3 ] threadName = threadName7 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName7 ] threadName = threadName0 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName0 ] threadName = threadName6 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName6 ] threadName = threadName1 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName1 ] threadName = threadName2 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName2 ] threadName = threadName4 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName4 ] threadName = threadName5 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName5 ] threadName = threadName8 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName8 ]
咱們使用獲取到一個保存數據庫請求, tomcat會有一個線程去操做數據庫保存數據和響應數據給客戶, 而操做數據庫須要存在一個數據庫連接Connection
對象, 只要是同一個數據庫連接, 就能夠獲得同一個事務
但一個線程是如何獲取同一個Connection
從而獲取同一個事務 ?
方法其實很簡單, 使用 ThreadLocal
綁定在線程中, 相似於Map<Thread, Connection>
去存儲數據庫
get
方法分析數組
public T get() { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); // map不爲null if (map != null) { // 根據this獲取咱們的entry ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 若是map獲取爲空, 則初始化 return setInitialValue(); }
根據上面源碼分析發現ThreadLocal
底層使用的不是相似Map<Thread, Data>
這種結構而是
每一個線程都有一個屬於本身的ThreadLocalMap
結構
而他的結構是這樣的緩存
其中的table
數組在上面的 setInitialValue()
方法建立詳細源碼在這tomcat
private T setInitialValue() { // 這個方法在咱們的用例中沒寫, 因此默認放回 null T value = initialValue(); Thread t = Thread.currentThread(); // 獲取線程單獨的 ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { // 若是咱們初始化了initialValue() 方法, 那麼它默認初始化的值會被設置到這裏, // 可是實際上咱們用例爲null, 因此不會執行這段代碼 map.set(this, value); } else { // 線程ThreadLocalMap 沒被建立, 須要建立出來, // 其中的 table 數組在這裏被建立 createMap(t, value); } // 這裏我沒分析, 忽略了 if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value; }
他會在ThreadLocalMap
中調用構造方法初始化網絡
// 其中 firstValue是咱們的值 void createMap(Thread t, T firstValue) { // 關注下 this , 它是ThreadLocal t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 咱們的table在這裏被建立, INITIAL_CAPACITY == 16 table = new Entry[INITIAL_CAPACITY]; // 獲取不超過16的hashCode int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根據計算出來的HashCode設置到對應的table數組中, 這裏key是ThreadLocal, value是咱們的值 table[i] = new Entry(firstKey, firstValue); // 初始時, 已經有一個值了, 因此size = 1 size = 1; // 設置擴容閾值加載因子 threshold = len * 2 / 3; 默認爲長度的三分之二 setThreshold(INITIAL_CAPACITY); }
從這段代碼能夠發現, firstKey
實際上是咱們ThreadLocalMap
中的key
, 而firstKey
就是咱們的ThreadLocal
, 而value
就是咱們 initialValue()
方法返回的值, 這裏默認爲null
, 因此咱們能夠得出這樣一幅圖jvm
總結下
每一個線程都有一個屬於本身的ThreadLocalMap
類, 他用於關聯多個以ThreadLocal
對象爲key
, 以你的數據
爲value
的Entry
對象, 且該對象的key
是一個弱引用對象
接下來咱們分析下這個類Entry
, 它繼承了弱引用類WeakReference
ide
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { // ThreadLocal被設置爲弱引用 super(k); // 保存value value = v; } }
發現 ThreadLocal
被設置爲弱引用源碼分析
爲何前面的Entry
須要繼承弱引用類WeakReference
呢?
首先了解下什麼是引用
Object obj = new Object()
SoftReference
類實現軟引用, 通常用於相對比較重要但又能夠不用的對象, 好比: 緩存ReferenceQueue
)中或者對象中, 好比: ThreadLocalMap
的Entry
對象, 須要依附於ThreadLocal
纔可以不被刪除掉NIO
上, 用於引用直接內存, java提供類PhantomReference
來實現虛引用Entry
對象須要爲弱引用?答案很明顯, 防止內存泄漏[1], 咱們來詳細分析分析
首先, 咱們知道ThreadLocalMap
中存放的是一個一個Entry
對象, 而 Entry
對象中的key
(ThreadLocal
)被設計成弱引用若是key
被設置成null
(好比: 外部的測試用例中的private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
這個對象被設置爲 threadLocal = null
) 則, 你會發現此時Entry
的對象key = null value = xxxx
(此時這個Entry
實質上是沒有用的, 連key
都給設置成null
, 它的value
還有什麼用?) 而ThreadLocalMap
中存儲的仍是Entry
對象的地址, 此Entry
不會被回收, 但Entry
對象的key
被設置成弱引用, 就不同了, 直接會被回收掉它
[1]內存泄漏: 程序中己動態分配的堆內存因爲某種緣由程序未釋放或沒法釋放,形成系統內存的浪費,致使程序運行速度減慢甚至系統崩潰等嚴重後果
再次強調, 下面這段話別信, 仔細看到最後, 你會發現這被打臉了
其實應該是沒什麼問題了(__被本身打臉了, 別信這句話__), 只不過不少網友以爲Entry
中的key
雖然是弱引用, 但Entry
可能不會被回收, 由於entry
的value
是強引用, 可能致使線程下的entry
沒法被回收掉, 最好推薦使用threadLocal.remove
方法刪除掉, 前面說的threadLocal = null
方法不推薦使用, 那麼爲了以防萬一吧, 仍是手動調用下remove
方法比較好一點
下面是我對threadLocal = null
方式的代碼測試:
public class ThreadLocalDemo { private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>() { @Override protected String initialValue() { return "1"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>() { @Override protected String initialValue() { return "2"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException { // 獲取ThreadLocalMap Thread thread = Thread.currentThread(); Class<? extends Thread> clazz = thread.getClass(); Field threadLocals = clazz.getDeclaredField("threadLocals"); threadLocals.setAccessible(true); Object threadLocalsObj = threadLocals.get(thread); // 獲取ThreadLocalMap下的table數組 Class<?> threadLocalsMapClass = threadLocalsObj.getClass(); Field tableField = threadLocalsMapClass.getDeclaredField("table"); tableField.setAccessible(true); Object[] tableObj = (Object[]) tableField.get(threadLocalsObj); threadLocal1.set("zhazha"); threadLocal2.set("xixi"); System.out.println(threadLocal1.get()); System.out.println(threadLocal2.get()); // 在這裏下一個斷點看看ThreadLocal被回收, Entry是否被回收 threadLocal1 = null; threadLocal2 = null; System.gc(); Thread.sleep(5000); System.out.println(tableObj); System.out.println("主線程結束"); } }
輸出是這樣的:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.zhazha.threadlocal.ThreadLocalDemo (file:/D:/program/codes/java/Concurrentcy/reviewjuc/target/classes/) to field java.lang.Thread.threadLocals WARNING: Please consider reporting this to the maintainers of com.zhazha.threadlocal.ThreadLocalDemo WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release zhazha xixi [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;@aecb35a 主線程結束
若是上面的代碼不調用gc
方法, 很長一段時間內不會被回收, 應該是jvm gc
還沒開始被動回收
但!!!但!!!但!!! 看調試代碼
數組中的referent
字段仍是存在的, 下圖是gc
回收以前查看數組中的元素髮現, 字段referent
(也就是ThreadLocal
) 它還在
在gc
方法執行完畢後, referent
被回收掉了, referent = null
了
可是那個對象怎麼回事??? 沒被回收掉?? 打臉了??? 求助廣大網友給我看看
那讓咱們試試 remove
方法試試?
好了, 直接沒了, 找不到那兩個屬性了
這An illegal reflective access operation has occurred
這個問題怎麼幫? 這回真不知道了, 應該不影響咱們的代碼麼?
算了爲了把這個紅色的字改沒掉, 改了改源碼
public class ThreadLocalDemo { private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>() { @Override protected String initialValue() { return "1"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>() { @Override protected String initialValue() { return "2"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; private static Unsafe unsafe; static { Class<Unsafe> unsafeClass = Unsafe.class; Unsafe unsafe = null; try { Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); ThreadLocalDemo.unsafe = (Unsafe) unsafeField.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException, NoSuchFieldException { Thread thread = Thread.currentThread(); long threadLocalsFieldOffset = unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals")); Object threadLocalMapObj = unsafe.getObject(thread, threadLocalsFieldOffset); long tableOffset = unsafe.objectFieldOffset(threadLocalMapObj.getClass().getDeclaredField("table")); Object tableObj = unsafe.getObject(threadLocalMapObj, tableOffset); threadLocal1.set("zhazha"); threadLocal2.set("xixi"); System.out.println(threadLocal1.get()); System.out.println(threadLocal2.get()); threadLocal1 = null; threadLocal2 = null; // threadLocal1.remove(); // threadLocal2.remove(); System.gc(); System.out.println(tableObj); System.out.println("主線程結束"); } }
好了沒這個問題了
zhazha xixi threadLocal1被回收 threadLocal1被回收 [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;@7dc222ae 主線程結束 與目標VM斷開鏈接, 地址爲: ''127.0.0.1:58958',傳輸: '套接字'', 傳輸: '{1}' 進程已結束,退出代碼0