解決子線程無法訪問父線程中通過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());
}
}運行結(jié)果:

InheritableThreadLocal就是為了解決這個不可見問題而生的~
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());
}
}運行結(jié)果:

二、分析原因
那么, 究竟是什么原因?qū)е碌淖泳€程無法訪問父線程中的ThreadLocal變量呢,接下來研究ThreadLocal這個類的源碼。
先看一下這個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)鍵注釋
博主在類的文檔注釋上提取了幾個關(guān)于ThreadLocal的重要的說明
This class provides thread-local variables.
這個類提供了線程本地變量
ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.
ThreadLocal實例提供了可以關(guān)聯(lián)一個線程的靜態(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對象可以訪問,每個線程都擁有一個線程本地變量的副本的引用;
在線程執(zhí)行完方法之后,所有線程本地變量都會被gc(垃圾收集器)回收,除非有其他變量引用了它。
2、研究源碼
經(jīng)過上面博主的大致翻譯,已經(jīng)對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對象
ThreadLocalMap map = getMap(thread);
// map不為null,設(shè)置初始值到map中
if (map != null) {
map.set(this, value);
} else {
// map是null,創(chuàng)建一個map
createMap(t, value);
}
}- getMap()
看樣子和線程類Thead里的一個threadLocals變量有關(guān)。
ThreadLocal.ThreadLocalMap getMap(Thread thread) {
// 這個方法就是返回Thread的threadLocals變量的值
return thread.threadLocals;
}ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類
public class ThreadLocal<T> {
static class ThreadLocalMap {
}
}- set()
ThreadLocal.ThreadLocalMap的set()方法,有點復(fù)雜,類似HashMap的底層源碼實現(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變量中,這個變量應(yīng)該是一個Map派生類的實例。
get()方法
public T get() {
Thread thread = Thread.currentThread(); // 獲取當(dāng)前線程
// 通過getMap()方法獲取一個ThreadLocalMap對象
// 因為這個類是以Map結(jié)尾的,推測是java.util.Map的派生類
ThreadLocalMap map = getMap(thread);
// 獲取到的map不為null
if (map != null) {
// 獲取Map的Entry
// 說明我們的推測大概率是正確的,因為HashMap中也有個Enty,而且也有value()方法
ThreadLocalMap.Entry e = map.getEntry(this);
// 獲取的value不為空,則轉(zhuǎn)為指定的類型T(泛型)直接返回
if (e != null) {
T result = (T) e.value;
return result;
}
}
// 代碼運行到這里,說明獲取到的map是null
// 因為前面的if中已經(jīng)通過return結(jié)束方法
return setInitialValue();
}- getMap()
這個方法的代碼在前面set()方法里已經(jīng)看了~
- createMap()
這個方法的代碼在前面set()方法里已經(jīng)看了~
- setInitialValue()
這個方法和set()方法的代碼幾乎一模一樣。知識多了一個返回值~
T value = initialValue();
set()方法的代碼
return value;
private T setInitialValue() {
// 通過initialValue()方法獲取一個初始化值
T value = initialValue();
// 獲取當(dāng)前線程
Thread thread = Thread.currentThread();
// 根據(jù)當(dāng)前線程獲取ThreadLocalMap對象
ThreadLocalMap map = getMap(thread);
// map不為null,設(shè)置初始值到map中
if (map != null) {
map.set(this, value);
} else {
// map是null,創(chuàng)建一個map
createMap(thread, value);
}
// 返回初始值
return value;
}get()方法總結(jié)
綜合上面關(guān)于get()方法的源代碼,get()方法是從Thead線程類的threadLocals變量中獲取數(shù)據(jù)。
ThreadLocal.ThreadLocalMap
下面是ThreadLocalMap類的一些關(guān)鍵的代碼,很顯然,這個內(nèi)部類的結(jié)構(gòu)設(shè)計的和HashMap非常相似,通過一個數(shù)組table保存值,根據(jù)傳入的key進行特定的hash運算得到數(shù)組的下標(biāo),然后獲取對應(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í)的深入了一點,可能有些童鞋已經(jīng)懵了,但是回到set()和get()方法的代碼可以發(fā)現(xiàn)
不管你存儲值的代碼如何復(fù)雜,都是通過當(dāng)前線程作為key存儲在ThreadLocal的靜態(tài)內(nèi)部類ThreadLocalMap中的,知道這點其實就夠了。
在子線程中,由于通過new Thread()新開了一個線程,那么當(dāng)代碼執(zhí)行這個新開的子線程的run()方法內(nèi)部,當(dāng)前線程已經(jīng)不是剛開始執(zhí)行方法的線程了。
比如:在main方法中,執(zhí)行代碼的線程是main線程,也就是主線程,它的線程名就是main。
但是新開一個線程,它的線程名是通過Threa-0開始編號的
通過一個簡單的代碼了解一下
/**
* @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();
}
}運行結(jié)果:

也就是說,在不同線程中,當(dāng)前線程已經(jīng)發(fā)生了改變,調(diào)用ThreaLocal的get()方法的key變了,自然獲取不到我們當(dāng)初設(shè)置的變量值。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實現(xiàn)的連續(xù)奇數(shù)(n+2*x)是合數(shù)的算法題暴力算法
這篇文章主要介紹了Java實現(xiàn)的連續(xù)奇數(shù)(n+2*x)是合數(shù)的算法題暴力算法,本文包含運算結(jié)果和實現(xiàn)代碼,需要的朋友可以參考下2014-09-09
Spring之動態(tài)注冊bean的實現(xiàn)方法
這篇文章主要介紹了Spring之動態(tài)注冊bean的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
java?list和map切割分段的實現(xiàn)及多線程應(yīng)用案例
這篇文章主要為大家介紹了java?list和map切割分段的實現(xiàn)及多線程應(yīng)用案例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12
IDEA中l(wèi)og4j 無法輸出到本地 properties配置無效問題
這篇文章主要介紹了IDEA中l(wèi)og4j 無法輸出到本地 properties配置無效問題,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2019-10-10
SpringBoot集成Zipkin實現(xiàn)分布式全鏈路監(jiān)控
這篇文章主要介紹了SpringBoot集成Zipkin實現(xiàn)分布式全鏈路監(jiān)控的方法啊,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-09-09
springboot項目連接不上nacos配置,報‘url‘異常問題
這篇文章主要介紹了springboot項目連接不上nacos配置,報‘url‘異常問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06

