ThreadLocal是一個線程內部的數據存儲類舵盈,通過它可以在同一個線程內共享數據驯击。
ThreadLocal原理
代碼分析
從demo說起
/*
* 持有ThreadLocal的類
**/
public class ThreadLocalContext {
private static final ThreadLocal<String> NAME_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<Integer> AGE_LOCAL = new ThreadLocal<>();
public static void setName(String name) {
NAME_LOCAL.set(name);
}
public static void setAge(Integer age) {
AGE_LOCAL.set(age);
}
public static String getName() {
return NAME_LOCAL.get();
}
public static Integer getAge() {
return AGE_LOCAL.get();
}
}
//測試代碼
public static void main(String[] args) {
new Thread(()->{
ThreadLocalContext.setAge(1);
ThreadLocalContext.setName("test");
print();
}).start();
new Thread(()->{
ThreadLocalContext.setAge(10);
ThreadLocalContext.setName("xixi");
print();
}).start();
}
private static void print(){
String desc=ThreadLocalContext.getName()+"-"+ThreadLocalContext.getAge();
System.out.println(Thread.currentThread().getName()+":"+desc);
}
根據上面的demo代碼笋颤,我們就從get/set進行分析ThreadLocal的代碼
從上面代碼中可以看到鸽捻,ThreadLocalContext持有了兩個ThreadLocal對象NAME_LOCAL
和AGE_LOCAL
片仿。
ThreadLocal.set()
我們首先從setName和setAge說起巷折。
public class ThreadLocal{
//set 方法
public void set(T value) {
Thread t = Thread.currentThread();//1.獲取當前線程
ThreadLocalMap map = getMap(t);//2.從當前線程中獲取到ThreadLocalMap
if (map != null)
map.set(this, value); //3.如果不為空甸赃,將當前的ThreadLocal對象為key瀑罗,存儲到當前線程的ThreadLocalMap中
else
createMap(t, value); //4.如果為空闽颇,需要為當前Thread創(chuàng)建ThreadLocalMap對象
}
}
可以看到上面的代碼盾戴,為什么可以隔離每個線程的數據。
就是因為ThreadLocalMap是Thread的成員變量兵多,所以數據是存儲到Thread對象里的捻脖,別的Thread是沒辦法獲取到其他線程的對象。
ThradLocal.get()
public class ThreadLocal{
public T get() {
Thread t = Thread.currentThread(); //1.獲取當前線程
ThreadLocalMap map = getMap(t); //2.通過當前線程獲取ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //3.根據當前的ThreadLocal為key中鼠,獲取Entry對象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//4. 初始化ThreadLocalMap
}
static class ThreadLocalMap{
//從上面步驟3過來
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
}
}
ThreadLocalMap是使用一個Entry[]存儲ThreadLocal對象可婶,每個ThreadLocal都有一個hashCode,每創(chuàng)建一個ThreadLocal其對應的hashCode會加上0x61c88647
援雇,以保證散列足夠的散矛渴。
即使這樣,仍然會出現hash沖突的情況惫搏,所以在getEntry的方法中具温,存在獲取到entry會當前ThreadLocal不一致的情況,此時會繼續(xù)執(zhí)行getEntryAfterMiss
方法筐赔。
如果再去分析源碼的話铣猩,會看到Entry實際上是繼承了WeakReference,來標識引用的ThreadLocal為弱引用茴丰,這樣能夠保證达皿,當ThreadLocal沒有其他引用的時候天吓,能夠正常被垃圾回收,不會因為Entry的引用峦椰,而無法回收龄寞。