背景知識
我們都知道InheritableThreadLocal類型的變量可以在父子線程之間傳遞煎娇。具體實現(xiàn)為:每個線程都維護了threadLocals
和inheritableThreadLocals
成員變量梧却,線程在初始化時會根據(jù)父線程的inheritableThreadLocals
創(chuàng)建自身的inheritableThreadLocals
竣贪。具體的創(chuàng)建由ThreadLocal類負責實現(xiàn)。
線程的threadLocals
和inheritableThreadLocals
成員變量都是ThreadLocal.ThreadLocalMap類型柒瓣,這個類在功能上類似于HashMap喳魏,是ThreadLocal或者InheritableThreadLocal類型變量的集合卖毁。
Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
init
是線程的初始化方法,注意這里省略了無關(guān)的一些代碼
Thread.java
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
* @param acc the AccessControlContext to inherit, or
* AccessController.getContext() if null
* @param inheritThreadLocals if {@code true}, inherit initial values for
* inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
……
Thread parent = currentThread();
……
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
……
}
問題描述
筆者最近在使用InheritableThreadLocal時無意中碰到了一個問題贩毕。先來看我自定義的ThreadLocal類型悯许,這個類的默認初始值為當前時間戳。
MyThreadLocal.java
import java.util.Date;
public class MyThreadLocal extends InheritableThreadLocal<Long> {
@Override
protected Long initialValue() {
return new Date().getTime();
}
}
在這里我創(chuàng)建了一個子線程myThread辉阶,然后打印當前線程的ThreadLocal變量的值先壕,過2秒后啟動子線程,由子線程打印這個ThreadLocal變量的值谆甜。
MyThread.java
public class MyThread extends Thread {
public static ThreadLocal<Long> tl = new MyThreadLocal();
@Override
public void run() {
System.out.println(tl.get());
}
public static void main(String args[]) {
MyThread myThread = new MyThread();
System.out.println(tl.get());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myThread.start();
}
}
由于MyThreadLocal繼承了InheritableThreadLocal垃僚,因此我期望兩者可以打印出相同的結(jié)果。然而無論運行多少遍這個程序规辱,實際結(jié)果總是相差2秒以上(非常接近2秒)谆棺。由此可見這個ThreadLocal變量并沒有從父線程傳遞到子線程。
問題分析
筆者試著通過分析源代碼來查找原因罕袋「氖纾可以看到線程中的threadLocals
變量默認為null,而且Thread類中并沒有任何對其賦值的地方浴讯,事實上通過注釋也可以發(fā)現(xiàn)threadLocals
和inheritableThreadLocals
都是由ThreadLocal類維護的
ThreadLocal類的createMap
方法為線程初始化threadLocals
變量
ThreadLocal.java
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
InheritableThreadLocal類的createMap
方法為線程初始化inheritableThreadLocals
變量
InheritableThreadLocal.java
/**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
而調(diào)用createMap
方法的地方為ThreadLocal的set
方法和setInitialValue
方法朵夏,后者被get
方法調(diào)用。ThreadLocal本身的構(gòu)造函數(shù)并沒有做任何事情榆纽,即使我們通過重寫initialValue
方法設(shè)置默認值侍郭。也就是說询吴,如果我們創(chuàng)建了ThreadLocal類型的對象后沒有調(diào)用set
方法設(shè)置初始值或者調(diào)用get
方法設(shè)置默認的初始值,那么這個對象不會被添加到線程的threadLocals
或者inheritableThreadLocals
中亮元。此時創(chuàng)建的子線程也不能獲取到父線程的InheritableThreadLocal類型的變量。由此導致了InheritableThreadLocal變量無法在父子線程之間傳遞唠摹。
當然解決方法也很簡單爆捞,在子線程創(chuàng)建之前調(diào)用set
方法設(shè)置變量的值或者調(diào)用get
方法獲取默認值即可。
結(jié)論
InheritableThreadLocal類型的變量要在子線程創(chuàng)建之前設(shè)置初始值勾拉,否則不能在父子線程之間共享煮甥。