1.簡介
在使用多線程場景中嘲恍,對(duì)同一個(gè)共享變量進(jìn)行訪問息堂,會(huì)容易出現(xiàn)并發(fā)問題嚷狞,造成所得到的結(jié)果不正確,通常采取的辦法是在訪問這個(gè)共享變量進(jìn)行同步操作荣堰,一般加鎖操作床未。
但是,眾所周知振坚,加鎖操作薇搁,一定程度上會(huì)減低程序性能。假如這個(gè)變量并不需要保證在各個(gè)線程共享渡八,各個(gè)線程都可以獨(dú)立操作這個(gè)變量時(shí)啃洋,不用在意是否在讀取這個(gè)變量時(shí)候,這個(gè)變量已被其他線程修改了值屎鳍,那么完全可以不用同步操作宏娄,這就涉及到我們今天要講的 ThreadLocal 這個(gè)類。
ThreaLocal 是 JKD 包提供逮壁,位于 java.lang 包下孵坚,它提供線程本地變量,簡而言之,就是為每一個(gè)線程創(chuàng)建一個(gè)變量副本卖宠,線程操作是自己本地內(nèi)存中的值巍杈,從而使每個(gè)線程都可以對(duì)這個(gè)變量為所欲為了。典型的應(yīng)用場景是逗堵,數(shù)據(jù)庫打開關(guān)閉操作秉氧。
2.使用
ThreadLocal 使用也非常簡單,下面寫一個(gè)簡單例子:
@Test
public void testThreadLocal() {
final ThreadLocal<String> local = new ThreadLocal<>();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
local.set("線程A對(duì)變量為所欲為");
System.out.println("線程A:" + local.get());
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
local.set("線程B對(duì)變量為所欲為");
System.out.println("線程B:" + local.get());
}
});
threadA.start();
threadB.start();
}
//輸出:
//線程A:線程A對(duì)變量為所欲為
//線程B:線程B對(duì)變量為所欲為
以上程序可以看出蜒秤,兩個(gè)線程都通過 set 對(duì) local 設(shè)置值汁咏,之后通過 get 讀取 local 值,但是都是操作自己本地副本作媚。
3.原理
ThreaLocal 的代碼量很少攘滩,原理也比較簡單,簡單來說纸泡,內(nèi)部維護(hù)了一個(gè) Map漂问,這個(gè) Map 是 ThreadLocal 一個(gè)內(nèi)部靜態(tài)類,叫 ThreadLocalMap女揭。因?yàn)槊恳粋€(gè)線程都可以關(guān)聯(lián)多個(gè) ThreadLocal 變量蚤假,所以設(shè)計(jì)成 Map結(jié)構(gòu)。接下來看看各個(gè)方法主要實(shí)現(xiàn)邏輯:
3.1.void set(T value)
public void set(T value) {
//1.獲取當(dāng)前線程
Thread t = Thread.currentThread();
//2.以當(dāng)前線程 t 為 key吧兔,獲取當(dāng)前 threadLocals 變量
ThreadLocalMap map = getMap(t);
//3.如果存在磷仰,把 value 設(shè)置到 threadLocals 中
if (map != null)
map.set(this, value);
//4.如果不存在,創(chuàng)建當(dāng)前線程對(duì)應(yīng)的 Map
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//獲取線程自己的變量 threadLocals
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//創(chuàng)建當(dāng)前線程的 threadLocals 變量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.2.T get(T value)
public T get() {
//1.獲取當(dāng)前線程
Thread t = Thread.currentThread();
//2.以當(dāng)前線程 t 為 key境蔼,獲取當(dāng)前 threadLocals 變量
ThreadLocalMap map = getMap(t);
//3.如果不為 null灶平,就返回 key 對(duì)應(yīng)的本地變量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//4.如果為null,就進(jìn)行初始化
return setInitialValue();
}
//邏輯和 void set() 差不多
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
3.3.void remove()
public void remove() {
//1.以當(dāng)前線程 t 為 key箍土,獲取當(dāng)前 threadLocals 變量
ThreadLocalMap m = getMap(Thread.currentThread());
//2.如果不為 null逢享,刪除本地變量
if (m != null)
m.remove(this);
}
4.問題
在使用 ThreadLocal 中,由于每一個(gè)線程都持有一個(gè) threadLocals 變量吴藻,他是一個(gè)HashMap類型瞒爬,key 為當(dāng)前線程,value 為變量副本沟堡,如果一個(gè)線程一直不消亡疮鲫,會(huì)一直持有變量副本,容易造成內(nèi)存泄露弦叶,所以俊犯,為了保險(xiǎn)起見,在使用完變量副本之后伤哺,要調(diào)用 remove 方法來刪除本地變量副本燕侠。