一溜哮、ThreadLocal原理
簡單說 ThreadLocal 就是一種以空間換時間的做法,在每個 Thread 里面維護(hù)了一個以開放定址法實現(xiàn)的ThreadLocal.ThreadLocalMap圈澈,把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享尘惧,自然就沒有線程安全方面的問題了康栈。
JDK1.2 就提供了java.lang.ThreadLocal。ThreadLocal 為解決多線程程序的并發(fā)問題提供了一種新的思路喷橙。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序啥么,ThreadLocal 并不是一個 Thread,而是 Thread 的局部變量贰逾。
ThreadLocal 用于保存某個線程共享變量:對于同一個 static ThreadLocal悬荣,其為每個使用該變量的線程提供獨立的變量副本,不同線程只能從中 get疙剑、set 和 remove 自己的變量氯迂,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本言缤。從線程的角度看嚼蚀,目標(biāo)變量就像是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思管挟。ThreadLocal 類接口很簡單轿曙,只有四個方法:
-
void set(Object value)
設(shè)置當(dāng)前線程的線程局部變量的值。 -
public Object get()
返回當(dāng)前線程所對應(yīng)的線程局部變量僻孝。 -
public void remove()
將當(dāng)前線程局部變量的值刪除导帝,目的是為了減少內(nèi)存的占用,該方法是 JDK5.0 新增的方法穿铆。需要指出的是您单,當(dāng)線程結(jié)束后,對應(yīng)該線程的局部變量將自動被垃圾回收悴务,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作睹限,但它可以加快內(nèi)存回收的速度。 -
protected Object initialValue()
返回該線程局部變量的初始值讯檐,該方法是一個 protected 的方法羡疗,顯然是為了讓子類覆蓋而設(shè)計的。這個方法是一個延遲調(diào)用方法别洪,在線程第 1 次調(diào)用 get() 或 set(Object) 時才執(zhí)行叨恨,并且僅執(zhí)行 1 次。ThreadLocal 中的缺省實現(xiàn)直接返回一個 null挖垛。
值得一提的是痒钝,在 JDK5.0 中秉颗,ThreadLocal 已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)?ThreadLocal<T>送矩。API 方法也相應(yīng)進(jìn)行了調(diào)整蚕甥,新版本的 API 方法分別是 void set(T value)、T get() 以及 T initialValue()栋荸。
ThreadLocal 如何為每一個線程維護(hù)變量的副本菇怀?思路很簡單:在 ThreadLocal 類中有一個 Map,用于存儲每一個線程的變量副本晌块,Map 中元素的鍵為線程對象爱沟,而值對應(yīng)線程的變量副本。
二匆背、用法
1??線程共享變量緩存如下:Thread.ThreadLocalMap<ThreadLocal, Object>;
- Thread:當(dāng)前線程呼伸,可以通過 Thread.currentThread() 獲取。
- ThreadLocal:static ThreadLocal 變量钝尸。
- Object:當(dāng)前線程共享變量括享。
調(diào)用 ThreadLocal.get() 時,實際上是從當(dāng)前線程中獲取 ThreadLocalMap<ThreadLocal, Object>蝶怔,然后根據(jù)當(dāng)前 ThreadLocal 獲取當(dāng)前線程共享變量 Object奶浦。ThreadLocal.set兄墅,ThreadLocal.remove 實際上是同樣的道理踢星。
2??這種存儲結(jié)構(gòu)的好處:
- 線程死去的時候,線程共享變量 ThreadLocalMap 則銷毀隙咸。
- ThreadLocalMap<ThreadLocal,Object> 鍵值對數(shù)量為 ThreadLocal 的數(shù)量沐悦,一般來說 ThreadLocal 數(shù)量很少。相比在 ThreadLocal 中用 Map<Thread, Object> 鍵值對存儲線程共享變量(Thread 數(shù)量一般來說比 ThreadLocal 數(shù)量多)五督,性能提高很多藏否。
3??關(guān)于ThreadLocalMap<ThreadLocal, Object>
弱引用問題:
當(dāng)線程沒有結(jié)束,但是 ThreadLocal 已經(jīng)被回收充包,則可能導(dǎo)致線程中存在ThreadLocalMap<null, Object> 的鍵值對副签,造成內(nèi)存泄露。(ThreadLocal 被回收基矮,ThreadLocal 關(guān)聯(lián)的線程共享變量還存在淆储。)
雖然 ThreadLocal 的 get/set 方法可以清除 ThreadLocalMap 中 key 為 null 的 value,但是 get/set 方法在內(nèi)存泄露后并不會必然調(diào)用家浇,所以為了防止此類情況的出現(xiàn)本砰,有兩種手段:
- 使用完線程共享變量后,顯示調(diào)用 ThreadLocalMap.remove 方法清除線程共享變量钢悲。
- JDK 建議 ThreadLocal 定義為 private static点额,這樣 ThreadLocal 的弱引用問題則不存在了舔株。
//ThreadLocal用法
public class MyThreadLocal {
private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
//ThreadLocal沒有被當(dāng)前線程賦值時或當(dāng)前線程剛調(diào)用remove方法后調(diào)用get方法,返回此方法值
@Override
protected Object initialValue() {
System.out.println("調(diào)用get()時还棱,當(dāng)前線程共享變量沒有設(shè)置载慈,調(diào)initialValue獲取默認(rèn)值");
return null;
}
};
public static void main(String[] args) {
new Thread(new MyIntegerTask("IntegerTask1")).start();
new Thread(new MyStringTask("StringTask1")).start();
new Thread(new MyIntegerTask("IntegerTask2")).start();
new Thread(new MyStringTask("StringTask2")).start();
}
public static class MyIntegerTask implements Runnable {
private String name;
MyIntegerTask(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
// ThreadLocal.get方法獲取線程變量
if (null == MyThreadLocal.threadLocal.get()) {
// ThreadLocal.et方法設(shè)置線程變量
MyThreadLocal.threadLocal.set(0);
System.out.println("線程" + name + ": 0");
} else {
int num = (Integer) MyThreadLocal.threadLocal.get();
MyThreadLocal.threadLocal.set(num + 1);
System.out.println("線程" + name + ":" + MyThreadLocal.threadLocal.get());
if (i == 3) {
MyThreadLocal.threadLocal.remove();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class MyStringTask implements Runnable {
private String name;
MyStringTask(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (null == MyThreadLocal.threadLocal.get()) {
MyThreadLocal.threadLocal.set("a");
System.out.println("線程" + name + ":a");
} else {
String str = (String) MyThreadLocal.threadLocal.get();
MyThreadLocal.threadLocal.set(str + "a");
System.out.println("線程" + name + ":" + MyThreadLocal.threadLocal.get());
}
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
結(jié)果如下:
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置珍手,調(diào)用initialValue獲取默認(rèn)值娃肿!
線程IntegerTask1: 0
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置珠十,調(diào)用initialValue獲取默認(rèn)值料扰!
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置焙蹭,調(diào)用initialValue獲取默認(rèn)值晒杈!
線程IntegerTask2: 0
線程StringTask1: a
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置孔厉,調(diào)用initialValue獲取默認(rèn)值拯钻!
線程StringTask2: a
線程StringTask1: aa
線程StringTask2: aa
線程IntegerTask1: 1
線程IntegerTask2: 1
線程StringTask1: aaa
線程StringTask2: aaa
線程IntegerTask1: 2
線程IntegerTask2: 2
線程StringTask2: aaaa
線程StringTask1: aaaa
線程IntegerTask1: 3
線程IntegerTask2: 3
線程StringTask1: aaaaa
線程StringTask2: aaaaa
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置撰豺,調(diào)用initialValue獲取默認(rèn)值粪般!
線程IntegerTask1: 0
調(diào)用get方法時,當(dāng)前線程共享變量沒有設(shè)置污桦,調(diào)用initialValue獲取默認(rèn)值亩歹!
線程IntegerTask2: 0