寫(xiě)在前面
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問(wèn)題提供了一種新的思路硕蛹。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫(xiě)出優(yōu)美的多線程程序掌实。當(dāng)使用ThreadLocal維護(hù)變量時(shí)额衙,ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本虏杰,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本腋腮。從線程的角度看雀彼,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思即寡。本篇文章將帶你詳細(xì)的剖析一下ThreadLocal徊哑,以及在開(kāi)發(fā)中的使用。
代碼分析
首先ThreadLocal這個(gè)類是位于java.lang這個(gè)包中嘿悬,具體源碼可以自行查看实柠。那么ThreadLocal到底是干嘛用的呢?其實(shí)它主要就用來(lái)在當(dāng)前線程中存取變量用的善涨,存取變量就存取變量怎么多了一句當(dāng)前線程中?不要急接著往下看。說(shuō)到存取變量我們平時(shí)可能用到的例如:List草则,Map钢拧,數(shù)組等,那么ThreadLocal又是通過(guò)什么樣方式進(jìn)行變量的存取呢炕横?答案就是Map(鍵值對(duì)),沒(méi)錯(cuò)就是Map源内,而這個(gè)Map不是其他的Map而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類份殿,在每一個(gè)線程Thread中都有一個(gè)ThreadLocalMap的成員變量,不信的話看一下Thread類的源碼:
public class Thread implements Runnable {
.......這里省略掉之前的代碼
ThreadLocal.ThreadLocalMap threadLocals = null;
.......這里省略掉之后的代碼
}
這里你會(huì)發(fā)現(xiàn)在Thread類中確實(shí)是有一個(gè)ThreadLocalMap的成員變量threadLocals膜钓,可以很明確的告訴你ThreadLocal就是在操作這個(gè)Thread中的threadLocals嗽交,來(lái)進(jìn)行變量的存取,置于為什么ThreadLocal能操作Thread中的threadLocals颂斜,以及如何操作的后面通過(guò)源碼的分析更進(jìn)一步的了解夫壁。截止到目前你需要知道以下幾點(diǎn):
- 1、 ThreadLocal是在用于存取變量的
- 2沃疮、 ThreadLocal是在當(dāng)前線程中存取變量的
- 3盒让、 ThreadLocal是采用Map,也就是鍵值對(duì)的形式進(jìn)行變量存取的
我們先看看如何使用的司蔬,然后在分析分析源碼邑茄,例如下面就是使用ThreadLocal的一個(gè)例子:
public class MyClass {
//定義了兩個(gè)ThreadLocal分別為:local1,local2
private static ThreadLocal<String> local1 = new ThreadLocal<>();
private static ThreadLocal<Boolean> local2 = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
public static void main(String[] args) {
System.out.println("local1:" + local1.get());//運(yùn)行結(jié)果:local1:null
System.out.println("local2:" + local2.get());//運(yùn)行結(jié)果:local2:false
local1.set("我是local1");
local2.set(true);
System.out.println("local1:" + local1.get());//運(yùn)行結(jié)果:local1:我是local1
System.out.println("local2:" + local2.get());//運(yùn)行結(jié)果:local2:true
}
}
通過(guò)以上代碼你會(huì)發(fā)現(xiàn)我在未進(jìn)行任何存儲(chǔ)操作即未調(diào)用set(T t)方法之前:local1返回的是null俊啼,local2返回的卻是false;這就和local2重寫(xiě)了initialValue方法有關(guān)肺缕,具體請(qǐng)看ThreadLocal源碼分析(這里剔除掉了和我們使用不相干的代碼):
package java.lang;//所在包
public class ThreadLocal<T> {
... //省略掉之前的代碼
//這個(gè)方法看到?jīng)]是protected的,也就是說(shuō)希望我們重寫(xiě)的授帕,默認(rèn)返回null
//初始化值搓谆,在未存儲(chǔ)任何值的前提下,去取值返回的結(jié)果豪墅,默認(rèn)返回null
protected T initialValue() {
return null;
}
//從ThreadLocalMap中獲取存儲(chǔ)的變量
public T get() {
//先獲取當(dāng)前的線程對(duì)象
Thread t = Thread.currentThread();
//再獲取線程對(duì)象中的ThreadLocalMap成員變量:threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果不為空泉手,則以自己注意是自己為鍵,來(lái)獲取對(duì)應(yīng)的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
//如果value不為空偶器,則直接返回
return (T)e.value;
}
return setInitialValue();
}
//往ThreadLocalMap中加入一個(gè)變量
public void set(T value) {
//先獲取當(dāng)前的線程對(duì)象
Thread t = Thread.currentThread();
//再獲取線程對(duì)象中的ThreadLocalMap成員變量:threadLocals
ThreadLocalMap map = getMap(t);
//判斷返回的ThreadLocalMap是否為空
if (map != null)
//如果不為空斩萌,則以自己注意是自己為鍵,value參數(shù)為值保寸到線程的ThreadLocalMap中
map.set(this, value);
else
//如果為空屏轰,則創(chuàng)建一個(gè)ThreadLocalMap颊郎,并以自己注意是自己為鍵,value參數(shù)為值霎苗,保存到該Map中同時(shí)將該Map賦值給當(dāng)前線程中的ThreadLocalMap成員變量:threadLocals
createMap(t, value);
}
//移除掉ThreadLocalMap存儲(chǔ)的變量
//該方法在jdk1.5加入
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
//省略ThreadLocalMap的具體實(shí)現(xiàn)
......
......
}
}
其實(shí)我們?cè)谑褂肨hreadLocal進(jìn)行變量存取操作的時(shí)候用到的也就是以上的幾個(gè)方法:initialValue()姆吭,get(),set()唁盏,remove()内狸。類比Map的操作就好,set就是存厘擂,get就是取昆淡,remove就是刪。
這里需要注意的是ThreadLocal進(jìn)行變量存儲(chǔ)的時(shí)候都是以自身(即this)為鍵來(lái)進(jìn)行存儲(chǔ)的刽严,所以存儲(chǔ)的是set方法而不是put或add昂灵,因?yàn)橐粋€(gè)ThreadLocal對(duì)象就是一個(gè)鍵,一個(gè)鍵在Map中只能對(duì)應(yīng)一個(gè)值;你要想往當(dāng)前線程的Map中存幾個(gè)不同的值眨补,那就需要幾個(gè)不同的鍵管削,即要?jiǎng)?chuàng)建幾個(gè)ThreadLocal對(duì)像,當(dāng)然不同線程中也可以同時(shí)使用同一個(gè)ThreadLocal作為鍵來(lái)存取不同的值撑螺,他們之間對(duì)值的操作互不影響含思。打個(gè)比方:
我用同一個(gè)身份證號(hào)(同一個(gè)ThreadLocal對(duì)象),在A商場(chǎng)(線程A)注冊(cè)了VIP,在B商場(chǎng)(線程B)也注冊(cè)了VIP实蓬,有一天我將A商場(chǎng)的VIP改變?yōu)榱薙VIP茸俭,那我在B商場(chǎng)依舊是VIP而不會(huì)因?yàn)槲腋淖兞薃商場(chǎng)的身份會(huì)影響到我B商場(chǎng)。
總結(jié)
ThreadLocal其實(shí)還是蠻有用的在Android的UI線程中Handler的Looper就是通過(guò)ThreadLocal存儲(chǔ)在當(dāng)前線程中的安皱,所以你只要在UI線程中創(chuàng)建的Handler用的都是之前已經(jīng)在該線程存儲(chǔ)好的Looper调鬓。一些開(kāi)源庫(kù)其實(shí)很多都用到了ThreadLocal,例如何洪輝的EventBus酌伊,其他的這里不做過(guò)多介紹腾窝,有興趣的可以去看看一些好的開(kāi)源庫(kù)的代碼,收獲會(huì)很大居砖。