解決子線程無(wú)法訪問父線程中通過ThreadLocal設(shè)置的變量問題
一、引出結(jié)論
學(xué)習(xí)過ThreadLocal的童鞋都知道,在子線程中,是無(wú)法訪問父線程通過ThreadLocal設(shè)置的變量的。
package thread; /** * @author heyunlin * @version 1.0 */ public class ThreadLocalExample { public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException { ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("hello"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("run()..."); /* * 子線程中無(wú)法訪問父線程中設(shè)置的ThreadLocal變量 */ System.out.println(threadLocal.get()); } }); thread.start(); thread.join(); System.out.println(thread); System.out.println(threadLocal.get()); } }
運(yùn)行結(jié)果:
InheritableThreadLocal就是為了解決這個(gè)不可見問題而生的~
package thread; /** * @author heyunlin * @version 1.0 */ public class InheritableThreadLocalExample { public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException { InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); threadLocal.set("hello"); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("run()..."); System.out.println(threadLocal.get()); } }); thread.start(); thread.join(); System.out.println(thread); System.out.println(threadLocal.get()); } }
運(yùn)行結(jié)果:
二、分析原因
那么, 究竟是什么原因?qū)е碌淖泳€程無(wú)法訪問父線程中的ThreadLocal變量呢,接下來(lái)研究ThreadLocal這個(gè)類的源碼。
先看一下這個(gè)ThreadLocal類上的文檔注釋,大概了解ThreadLocal是用來(lái)干什么的
/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). * * <p>For example, the class below generates unique identifiers local to each * thread. * A thread's id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. * <pre> * import java.util.concurrent.atomic.AtomicInteger; * * public class ThreadId { * // Atomic integer containing the next thread ID to be assigned * private static final AtomicInteger nextId = new AtomicInteger(0); * * // Thread local variable containing each thread's ID * private static final ThreadLocal<Integer> threadId = * new ThreadLocal<Integer>() { * @Override protected Integer initialValue() { * return nextId.getAndIncrement(); * } * }; * * // Returns the current thread's unique ID, assigning it if necessary * public static int get() { * return threadId.get(); * } * } * </pre> * <p>Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). * * @author Josh Bloch and Doug Lea * @since 1.2 */
1、關(guān)鍵注釋
博主在類的文檔注釋上提取了幾個(gè)關(guān)于ThreadLocal的重要的說(shuō)明
This class provides thread-local variables.
這個(gè)類提供了線程本地變量
ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.
ThreadLocal實(shí)例提供了可以關(guān)聯(lián)一個(gè)線程的靜態(tài)字段。
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
只要線程是存活狀態(tài),并且ThreadLocal對(duì)象可以訪問,每個(gè)線程都擁有一個(gè)線程本地變量的副本的引用;
在線程執(zhí)行完方法之后,所有線程本地變量都會(huì)被gc(垃圾收集器)回收,除非有其他變量引用了它。
2、研究源碼
經(jīng)過上面博主的大致翻譯,已經(jīng)對(duì)ThreadLocal有了一定的了解,接下來(lái)深入源碼去學(xué)習(xí)ThreadLocal的工作原理。
在不了解ThreadLocal的情況下,直接從我們直接調(diào)用的get()和set()方法入手。
public class ThreadLocal<T> { public T get() { Thread thread = Thread.currentThread(); ThreadLocalMap map = getMap(thread); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread thread = Thread.currentThread(); ThreadLocalMap map = getMap(thread); if (map != null) { map.set(this, value); } else { createMap(t, value); } } }
set()方法
public void set(T value) { // 獲取當(dāng)前線程 Thread thread = Thread.currentThread(); // 根據(jù)當(dāng)前線程獲取ThreadLocalMap對(duì)象 ThreadLocalMap map = getMap(thread); // map不為null,設(shè)置初始值到map中 if (map != null) { map.set(this, value); } else { // map是null,創(chuàng)建一個(gè)map createMap(t, value); } }
- getMap()
看樣子和線程類Thead里的一個(gè)threadLocals變量有關(guān)。
ThreadLocal.ThreadLocalMap getMap(Thread thread) { // 這個(gè)方法就是返回Thread的threadLocals變量的值 return thread.threadLocals; }
ThreadLocalMap是ThreadLocal的一個(gè)靜態(tài)內(nèi)部類
public class ThreadLocal<T> { static class ThreadLocalMap { } }
- set()
ThreadLocal.ThreadLocalMap的set()方法,有點(diǎn)復(fù)雜,類似HashMap的底層源碼實(shí)現(xiàn)。
private void set(ThreadLocal<?> key, Object value) { ThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) { rehash(); } }
- createMap()
void createMap(Thread thread, T firstValue) { // 初始化線程的threadLocals變量 thread.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); }
set()方法總結(jié)
根據(jù)set()方法的源代碼,set()方法是把值設(shè)置到Thead線程類的threadLocals變量中,這個(gè)變量應(yīng)該是一個(gè)Map派生類的實(shí)例。
get()方法
public T get() { Thread thread = Thread.currentThread(); // 獲取當(dāng)前線程 // 通過getMap()方法獲取一個(gè)ThreadLocalMap對(duì)象 // 因?yàn)檫@個(gè)類是以Map結(jié)尾的,推測(cè)是java.util.Map的派生類 ThreadLocalMap map = getMap(thread); // 獲取到的map不為null if (map != null) { // 獲取Map的Entry // 說(shuō)明我們的推測(cè)大概率是正確的,因?yàn)镠ashMap中也有個(gè)Enty,而且也有value()方法 ThreadLocalMap.Entry e = map.getEntry(this); // 獲取的value不為空,則轉(zhuǎn)為指定的類型T(泛型)直接返回 if (e != null) { T result = (T) e.value; return result; } } // 代碼運(yùn)行到這里,說(shuō)明獲取到的map是null // 因?yàn)榍懊娴膇f中已經(jīng)通過return結(jié)束方法 return setInitialValue(); }
- getMap()
這個(gè)方法的代碼在前面set()方法里已經(jīng)看了~
- createMap()
這個(gè)方法的代碼在前面set()方法里已經(jīng)看了~
- setInitialValue()
這個(gè)方法和set()方法的代碼幾乎一模一樣。知識(shí)多了一個(gè)返回值~
T value = initialValue();
set()方法的代碼
return value;
private T setInitialValue() { // 通過initialValue()方法獲取一個(gè)初始化值 T value = initialValue(); // 獲取當(dāng)前線程 Thread thread = Thread.currentThread(); // 根據(jù)當(dāng)前線程獲取ThreadLocalMap對(duì)象 ThreadLocalMap map = getMap(thread); // map不為null,設(shè)置初始值到map中 if (map != null) { map.set(this, value); } else { // map是null,創(chuàng)建一個(gè)map createMap(thread, value); } // 返回初始值 return value; }
get()方法總結(jié)
綜合上面關(guān)于get()方法的源代碼,get()方法是從Thead線程類的threadLocals變量中獲取數(shù)據(jù)。
ThreadLocal.ThreadLocalMap
下面是ThreadLocalMap類的一些關(guān)鍵的代碼,很顯然,這個(gè)內(nèi)部類的結(jié)構(gòu)設(shè)計(jì)的和HashMap非常相似,通過一個(gè)數(shù)組table保存值,根據(jù)傳入的key進(jìn)行特定的hash運(yùn)算得到數(shù)組的下標(biāo),然后獲取對(duì)應(yīng)數(shù)組下標(biāo)的值返回。
public class ThreadLocal<T> { static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; private ThreadLocal.ThreadLocalMap.Entry[] table; private int size = 0; private int threshold; private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); ThreadLocal.ThreadLocalMap.Entry e = table[i]; if (e != null && e.get() == key) { return e; } else { return getEntryAfterMiss(key, i, e); } } } }
三、代碼總結(jié)
文章稍微學(xué)習(xí)的深入了一點(diǎn),可能有些童鞋已經(jīng)懵了,但是回到set()和get()方法的代碼可以發(fā)現(xiàn)
不管你存儲(chǔ)值的代碼如何復(fù)雜,都是通過當(dāng)前線程作為key存儲(chǔ)在ThreadLocal的靜態(tài)內(nèi)部類ThreadLocalMap中的,知道這點(diǎn)其實(shí)就夠了。
在子線程中,由于通過new Thread()新開了一個(gè)線程,那么當(dāng)代碼執(zhí)行這個(gè)新開的子線程的run()方法內(nèi)部,當(dāng)前線程已經(jīng)不是剛開始執(zhí)行方法的線程了。
比如:在main方法中,執(zhí)行代碼的線程是main線程,也就是主線程,它的線程名就是main。
但是新開一個(gè)線程,它的線程名是通過Threa-0開始編號(hào)的
通過一個(gè)簡(jiǎn)單的代碼了解一下
/** * @author heyunlin * @version 1.0 */ public class Example { public static void main(String[] args) { System.out.println(Thread.currentThread()); new Thread() { @Override public void run() { System.out.println(Thread.currentThread()); } }.start(); } }
運(yùn)行結(jié)果:
也就是說(shuō),在不同線程中,當(dāng)前線程已經(jīng)發(fā)生了改變,調(diào)用ThreaLocal的get()方法的key變了,自然獲取不到我們當(dāng)初設(shè)置的變量值。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Java反射模擬實(shí)現(xiàn)Spring的IoC容器的操作
這篇文章主要介紹了使用Java反射模擬實(shí)現(xiàn)Spring的IoC容器的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題
這篇文章主要介紹了java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題的相關(guān)資料,需要的朋友可以參考下2017-05-05Java elasticSearch-api的具體操作步驟講解
這篇文章主要介紹了elasticSearch-api的具體操作步驟講解,本文通過詳細(xì)的步驟介紹和圖文代碼展示講解了該項(xiàng)技術(shù),需要的朋友可以參考下2021-06-06java.sql.SQLException問題解決以及注意事項(xiàng)
這篇文章主要給大家介紹了關(guān)于java.sql.SQLException問題解決以及注意事項(xiàng)的相關(guān)資料,這個(gè)問題其實(shí)很好解決,文中通過圖文將解決的辦法介紹的很詳細(xì),需要的朋友可以參考下2023-07-07SpringBoot實(shí)現(xiàn)優(yōu)雅停機(jī)的多種方式
優(yōu)雅停機(jī)(Graceful Shutdown)在現(xiàn)代微服務(wù)架構(gòu)中是非常重要的,它幫助我們確保在應(yīng)用程序停止時(shí),不會(huì)中斷正在進(jìn)行的請(qǐng)求或?qū)е聰?shù)據(jù)丟失,讓我們以通俗易懂的方式來(lái)講解這個(gè)概念以及如何在 Spring Boot 中實(shí)現(xiàn)它,需要的朋友可以參考下2025-01-01java byte與base64的互轉(zhuǎn)的實(shí)現(xiàn)示例
在項(xiàng)目開發(fā)中經(jīng)常用到,比如前端上送文件流(byte[])到后臺(tái)并轉(zhuǎn)成文件,本文主要介紹了java byte與base64的互轉(zhuǎn)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù)
這篇文章主要介紹了Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01