1.基本介紹
ThreadLocal提供了線程本地變量晌纫,它可以保證訪問到的變量屬于當(dāng)前線程税迷,每個線程都保存有一個變量副本,每個線程的變量都不同锹漱,而同一個線程在任何時候訪問這個本地變量的結(jié)果都是一致的箭养。
2.一個簡單的例子
ThreadLocal通常定義為private static類型。下面是一個簡單的例子了解基本用法哥牍,之后我們研究原理
public class ThreadLocalTest {
public static class MyRunnable implements Runnable {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal();
@Override
public void run() {
threadLocal.set((int) (Math.random() * 100D));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
}
}
public static void main(String[] args) {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
}
}
輸出(一次可能的輸出)
32
61
3.源碼分析
為了弄清為什么不同線程調(diào)用get方法的時候都是自己的本地變量我們就粗暴的直接看get方法是如何實(shí)現(xiàn)的
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();//1.獲取當(dāng)前調(diào)用的線程
ThreadLocalMap map = getMap(t);//2.獲取ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//3獲取真正的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
通過源碼我們可以發(fā)現(xiàn)毕泌,1.獲取當(dāng)前調(diào)用的線程2處獲取了一個map,這個具體的實(shí)現(xiàn)如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看出這是線程的一個內(nèi)部變量,ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類嗅辣。
//Thread.class
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
下面我們繼續(xù)分析到3處撼泛,這里拿到線程自己的map之后
map.getEntry(this)
也就是theadLocal自身作為key,然后獲取到對應(yīng)的值,看下ThreadLocalMap的部分源碼:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
可以看出存儲的key值本質(zhì)上是threadLocal.threadLocalHashCode.那這個threadLocalHashCode是怎么保證唯一性呢,繼續(xù)看
private final int threadLocalHashCode = nextHashCode();
//繼續(xù)跟蹤
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
到這一步澡谭,大體上也許就能明白基本原理愿题,當(dāng)然里面還有許多細(xì)節(jié),希望大家能夠自己看源碼來加深理解蛙奖。
簡單總結(jié):
- 1.重要的是理清Thread潘酗、ThreadLocal 和ThreadLocalMap三者之間的關(guān)系叔营。
- 2.ThreadLocalMap解決沖突的方法是線性探測法(不斷加1)熄驼,而不是HashMap的鏈地址法惋增,這一點(diǎn)也能從ThreadLocalMap源碼中Entry的結(jié)構(gòu)上看出來医清。
- 3.ThreadLocalMap中的Entry的key實(shí)現(xiàn)了弱引用华糖,這樣可以讓無用的key及時的回收作郭,Entry中Value的清理發(fā)生在調(diào)用set()或者remove() 方法是可能會觸發(fā)expungeStaleEntry()方法來清理無用的Entry呢蛤。
4.使用注意點(diǎn)
1.每次使用完ThreadLocal黑毅,都調(diào)用它的remove()方法祭衩,清除數(shù)據(jù)灶体。
2.當(dāng)和線程池使用時尤其要注意,沒有及時清理ThreadLocal掐暮,不僅是內(nèi)存泄漏的問題蝎抽,更嚴(yán)重的是可能導(dǎo)致業(yè)務(wù)邏輯出現(xiàn)問題。
3.線程池和InheritableThreadLocal使用更加需要注意,InheritableThreadLocal并不是線程安全的樟结,怎么說呢养交,這要從InheritableThreadLocal的實(shí)現(xiàn)原理講起。
在新創(chuàng)建線程的時候瓢宦,會調(diào)用new Thead()
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
然后繼續(xù)跟:
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
init(g, target, name, stackSize, null);
}
繼續(xù)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
......
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
///////////////////
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//////////////////
/**
* Construct a new map including all Inheritable ThreadLocals
* from given parent map. Called only by createInheritedMap.
*
* @param parentMap the map associated with parent thread.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal key = e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
最終碎连,會把父線程的ThreadLocalMap 復(fù)制一份到子線程中,劃重點(diǎn)------關(guān)鍵的一句是:
Object value = key.childValue(e.value);
那么 childValue方法是實(shí)現(xiàn)是什么呢驮履?查看源碼發(fā)現(xiàn)InheritableThreadLocal 中的實(shí)現(xiàn)
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
默認(rèn)直接return 父線程的parentValue鱼辙,所以這樣就導(dǎo)致子線程和父線程指向的是同一個對象,這里并不是值傳遞玫镐,而是引用傳遞倒戏。而且,仔細(xì)看注釋
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
注釋說明如果有特定的需求,這個方法應(yīng)該被覆寫!!!
由于返回的是引用恐似,這樣我們線程池中很多的線程都會共用一個ThreadLocal,當(dāng)其中一個更新了ThreadLocal的值后杜跷,就會影響其他的線程,所以就出現(xiàn)了我們最開始提到的場景矫夷,那么如果我非要使用InheritableThreadLocal呢葛闷?方法就是覆寫 childValue方法,保證是值傳遞而不是引用傳遞双藕。