解決子線程無法訪問父線程中通過ThreadLocal設(shè)置的變量問題
一、引出結(jié)論
學(xué)習(xí)過ThreadLocal的童鞋都知道,在子線程中,是無法訪問父線程通過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()...");
/*
* 子線程中無法訪問父線程中設(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ū)е碌淖泳€程無法訪問父線程中的ThreadLocal變量呢,接下來研究ThreadLocal這個(gè)類的源碼。
先看一下這個(gè)ThreadLocal類上的文檔注釋,大概了解ThreadLocal是用來干什么的
/**
* 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的重要的說明
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有了一定的了解,接下來深入源碼去學(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é)尾的,推測是java.util.Map的派生類
ThreadLocalMap map = getMap(thread);
// 獲取到的map不為null
if (map != null) {
// 獲取Map的Entry
// 說明我們的推測大概率是正確的,因?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)行到這里,說明獲取到的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è)簡單的代碼了解一下
/**
* @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é)果:

也就是說,在不同線程中,當(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-08
java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題
這篇文章主要介紹了java 基礎(chǔ)之JavaBean屬性命名規(guī)范問題的相關(guān)資料,需要的朋友可以參考下2017-05-05
Java elasticSearch-api的具體操作步驟講解
這篇文章主要介紹了elasticSearch-api的具體操作步驟講解,本文通過詳細(xì)的步驟介紹和圖文代碼展示講解了該項(xiàng)技術(shù),需要的朋友可以參考下2021-06-06
java.sql.SQLException問題解決以及注意事項(xiàng)
這篇文章主要給大家介紹了關(guān)于java.sql.SQLException問題解決以及注意事項(xiàng)的相關(guān)資料,這個(gè)問題其實(shí)很好解決,文中通過圖文將解決的辦法介紹的很詳細(xì),需要的朋友可以參考下2023-07-07
SpringBoot實(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ù)丟失,讓我們以通俗易懂的方式來講解這個(gè)概念以及如何在 Spring Boot 中實(shí)現(xiàn)它,需要的朋友可以參考下2025-01-01
java 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-02
Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù)
這篇文章主要介紹了Mybatis如何使用正則模糊匹配多個(gè)數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

