什么是ThreadLocal
ThreadLocal拣技,簡(jiǎn)單翻譯過(guò)來(lái)就是本地線程脉幢,但是直接這么翻譯很難理解ThreadLocal的作用遭商,如果換一種說(shuō)法琼富,可以稱(chēng)為線程本地存儲(chǔ)盘榨。簡(jiǎn)單來(lái)說(shuō)娜搂,就是ThreadLocal為共享變量在每個(gè)線程中都創(chuàng)建一個(gè)副本漠秋,每個(gè)線程可以訪問(wèn)自己內(nèi)部的副本變量懈息。這樣做的好處是可以保證共享變量在多線程環(huán)境下訪問(wèn)的線程安全性
ThreadLocal的使用
沒(méi)有使用ThreadLocal時(shí)
通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)演示一下ThreadLocal的作用风范,這段代碼是定義了一個(gè)靜態(tài)的成員變量num
咨跌,然后通過(guò)構(gòu)造5個(gè)線程對(duì)這個(gè)num
做遞增
public class ThreadLocalDemo {
private static Integer num=0;
public static void main(String[] args) {
Thread[] threads=new Thread[5];
for(int i=0;i<5;i++){
threads[i]=new Thread(()->{
num+=5;
System.out.println(Thread.currentThread().getName()+" : "+num);
},"Thread-"+i);
}
for(Thread thread:threads){
thread.start();
}
}
}
運(yùn)行結(jié)果
Thread-0 : 5
Thread-1 : 10
Thread-2 : 15
Thread-3 : 20
Thread-4 : 25
每個(gè)線程都會(huì)對(duì)這個(gè)成員變量做遞增,如果線程的執(zhí)行順序不確定硼婿,那么意味著每個(gè)線程獲得的結(jié)果也是不一樣的锌半。
使用了ThreadLocal以后
通過(guò)ThreadLocal對(duì)上面的代碼做一個(gè)改動(dòng)
public class ThreadLocalDemo {
private static final ThreadLocal<Integer> local=new ThreadLocal<Integer>(){
protected Integer initialValue(){
return 0; //通過(guò)initialValue方法設(shè)置默認(rèn)值
}
};
public static void main(String[] args) {
Thread[] threads=new Thread[5];
for(int i=0;i<5;i++){
threads[i]=new Thread(()->{
int num=local.get().intValue();
num+=5;
System.out.println(Thread.currentThread().getName()+" : "+num);
},"Thread-"+i);
}
for(Thread thread:threads){
thread.start();
}
}
}
運(yùn)行結(jié)果
Thread-0 : 5
Thread-4 : 5
Thread-2 : 5
Thread-1 : 5
Thread-3 : 5
從結(jié)果可以看到,每個(gè)線程的值都是5加酵,意味著各個(gè)線程之間都是獨(dú)立的變量副本拳喻,彼此不相互影響.
ThreadLocal會(huì)給定一個(gè)初始值,也就是
initialValue()
方法猪腕,而每個(gè)線程都會(huì)從ThreadLocal中獲得這個(gè)初始化的值的副本冗澈,這樣可以使得每個(gè)線程都擁有一個(gè)副本拷貝
看到這里,估計(jì)有很多人都會(huì)和我一樣有一些疑問(wèn)
- 每個(gè)線程的變量副本是怎么存儲(chǔ)的?
- ThreadLocal是如何實(shí)現(xiàn)多線程場(chǎng)景下的共享變量副本隔離?
帶著疑問(wèn)陋葡,來(lái)看一下ThreadLocal這個(gè)類(lèi)的定義(默認(rèn)情況下,JDK的源碼都是基于1.8版本)
從ThreadLocal的方法定義來(lái)看,還是挺簡(jiǎn)單的亚亲。就幾個(gè)方法
- get: 獲取ThreadLocal中當(dāng)前線程對(duì)應(yīng)的線程局部變量
- set:設(shè)置當(dāng)前線程的線程局部變量的值
- remove:將當(dāng)前線程局部變量的值刪除
另外,還有一個(gè)initialValue()方法腐缤,在前面的代碼中有演示捌归,作用是返回當(dāng)前線程局部變量的初始值,這個(gè)方法是一個(gè)protected
方法岭粤,主要是在構(gòu)造ThreadLocal時(shí)用于設(shè)置默認(rèn)的初始值
set方法的實(shí)現(xiàn)
set方法是設(shè)置一個(gè)線程的局部變量的值惜索,相當(dāng)于當(dāng)前線程通過(guò)set設(shè)置的局部變量的值,只對(duì)當(dāng)前線程可見(jiàn)剃浇。
public void set(T value) {
Thread t = Thread.currentThread();//獲取當(dāng)前執(zhí)行的線程
ThreadLocalMap map = getMap(t); //獲得當(dāng)前線程的ThreadLocalMap實(shí)例
if (map != null)//如果map不為空巾兆,說(shuō)明當(dāng)前線程已經(jīng)有了一個(gè)ThreadLocalMap實(shí)例
map.set(this, value);//直接將當(dāng)前value設(shè)置到ThreadLocalMap中
else
createMap(t, value); //說(shuō)明當(dāng)前線程是第一次使用線程本地變量,構(gòu)造map
}
-
Thread.currentThread
獲取當(dāng)前執(zhí)行的線程 -
getMap(t)
,根據(jù)當(dāng)前線程得到當(dāng)前線程的ThreadLocalMap對(duì)象虎囚,這個(gè)對(duì)象具體是做什么的?稍后分析 - 如果map不為空角塑,說(shuō)明當(dāng)前線程已經(jīng)構(gòu)造過(guò)ThreadLocalMap,直接將值存儲(chǔ)到map中
- 如果map為空淘讥,說(shuō)明是第一次使用圃伶,調(diào)用
createMap
構(gòu)造
ThreadLocalMap是什么?
我們來(lái)分析一下這句話,ThreadLocalMap map=getMap(t)
獲得一個(gè)ThreadLocalMap對(duì)象,那這個(gè)對(duì)象是干嘛的呢?
其實(shí)不用分析窒朋,基本上也能猜測(cè)出來(lái)搀罢,Map是一個(gè)集合,集合用來(lái)存儲(chǔ)數(shù)據(jù)炼邀,那么在ThreadLocal中魄揉,應(yīng)該就是用來(lái)存儲(chǔ)線程的局部變量的。ThreadLocalMap
這個(gè)類(lèi)很關(guān)鍵拭宁。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
t.threadLocals實(shí)際上就是訪問(wèn)Thread類(lèi)中的ThreadLocalMap這個(gè)成員變量
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
從上面的代碼發(fā)現(xiàn)每一個(gè)線程都有自己?jiǎn)为?dú)的ThreadLocalMap實(shí)例,而對(duì)應(yīng)這個(gè)線程的所有本地變量都會(huì)保存到這個(gè)map內(nèi)
ThreadLocalMap是在哪里構(gòu)造?
在set
方法中瓣俯,有一行代碼createmap(t,value);
杰标,這個(gè)方法就是用來(lái)構(gòu)造ThreadLocalMap,從傳入的參數(shù)來(lái)看彩匕,它的實(shí)現(xiàn)邏輯基本也能猜出出幾分吧
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Thread t
是通過(guò)Thread.currentThread()
來(lái)獲取的表示當(dāng)前線程腔剂,然后直接通過(guò)new ThreadLocalMap
將當(dāng)前線程中的threadLocals
做了初始化
ThreadLocalMap是一個(gè)靜態(tài)內(nèi)部類(lèi),內(nèi)部定義了一個(gè)Entry對(duì)象用來(lái)真正存儲(chǔ)數(shù)據(jù)
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//構(gòu)造一個(gè)Entry數(shù)組驼仪,并設(shè)置初始大小
table = new Entry[INITIAL_CAPACITY];
//計(jì)算Entry數(shù)據(jù)下標(biāo)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//將`firstValue`存入到指定的table下標(biāo)中
table[i] = new Entry(firstKey, firstValue);
size = 1;//設(shè)置節(jié)點(diǎn)長(zhǎng)度為1
setThreshold(INITIAL_CAPACITY); //設(shè)置擴(kuò)容的閾值
}
//...省略部分代碼
}
分析到這里掸犬,基本知道了ThreadLocalMap長(zhǎng)啥樣了,也知道它是如何構(gòu)造的?那么我看到這里的時(shí)候仍然有疑問(wèn)
- Entry集成了
WeakReference
,這個(gè)表示什么意思? - 在構(gòu)造ThreadLocalMap的時(shí)候
new ThreadLocalMap(this, firstValue);
,key其實(shí)是this绪爸,this表示當(dāng)前對(duì)象的引用湾碎,在當(dāng)前的案例中,this指的是ThreadLocal<Integer> local
奠货。那么多個(gè)線程對(duì)應(yīng)同一個(gè)ThreadLocal實(shí)例介褥,怎么對(duì)每一個(gè)ThreadLocal對(duì)象做區(qū)分呢?
解惑WeakReference
weakReference表示弱引用递惋,在Java中有四種引用類(lèi)型柔滔,強(qiáng)引用、弱引用萍虽、軟引用睛廊、虛引用。
使用弱引用的對(duì)象杉编,不會(huì)阻止它所指向的對(duì)象被垃圾回收器回收超全。
在Java語(yǔ)言中, 當(dāng)一個(gè)對(duì)象o被創(chuàng)建時(shí), 它被放在Heap里. 當(dāng)GC運(yùn)行的時(shí)候, 如果發(fā)現(xiàn)沒(méi)有任何引用指向o, o就會(huì)被回收以騰出內(nèi)存空間. 也就是說(shuō), 一個(gè)對(duì)象被回收, 必須滿足兩個(gè)條件:
- 沒(méi)有任何引用指向它
- GC被運(yùn)行.
這段代碼中,構(gòu)造了兩個(gè)對(duì)象a,b王财,a是對(duì)象DemoA的引用卵迂,b是對(duì)象DemoB的引用,對(duì)象DemoB同時(shí)還依賴對(duì)象DemoA绒净,那么這個(gè)時(shí)候我們認(rèn)為從對(duì)象DemoB是可以到達(dá)對(duì)象DemoA的见咒。這種稱(chēng)為強(qiáng)可達(dá)(strongly reachable)
DemoA a=new DemoA();
DemoB b=new DemoB(a);
如果我們?cè)黾右恍写a來(lái)將a對(duì)象的引用設(shè)置為null,當(dāng)一個(gè)對(duì)象不再被其他對(duì)象引用的時(shí)候挂疆,是會(huì)被GC回收的改览,但是對(duì)于這個(gè)場(chǎng)景來(lái)說(shuō)下翎,即時(shí)是a=null,也不可能被回收宝当,因?yàn)镈emoB依賴DemoA视事,這個(gè)時(shí)候是可能造成內(nèi)存泄漏的
DemoA a=new DemoA();
DemoB b=new DemoB(a);
a=null;
通過(guò)弱引用,有兩個(gè)方法可以避免這樣的問(wèn)題
//方法1
DemoA a=new DemoA();
DemoB b=new DemoB(a);
a=null;
b=null;
//方法2
DemoA a=new DemoA();
WeakReference b=new WeakReference(a);
a=null;
對(duì)于方法2來(lái)說(shuō)庆揩,DemoA只是被弱引用依賴俐东,假設(shè)垃圾收集器在某個(gè)時(shí)間點(diǎn)決定一個(gè)對(duì)象是弱可達(dá)的(weakly reachable)(也就是說(shuō)當(dāng)前指向它的全都是弱引用),這時(shí)垃圾收集器會(huì)清除所有指向該對(duì)象的弱引用订晌,然后把這個(gè)弱可達(dá)對(duì)象標(biāo)記為可終結(jié)(finalizable)的虏辫,這樣它隨后就會(huì)被回收。
試想一下如果這里沒(méi)有使用弱引用锈拨,意味著ThreadLocal的生命周期和線程是強(qiáng)綁定砌庄,只要線程沒(méi)有銷(xiāo)毀,那么ThreadLocal一直無(wú)法回收奕枢。而使用弱引用以后娄昆,當(dāng)ThreadLocal被回收時(shí),由于Entry的key是弱引用缝彬,不會(huì)影響ThreadLocal的回收防止內(nèi)存泄漏萌焰,同時(shí),在后續(xù)的源碼分析中會(huì)看到跌造,ThreadLocalMap本身的垃圾清理會(huì)用到這一個(gè)好處杆怕,方便對(duì)無(wú)效的Entry進(jìn)行回收
解惑ThreadLocalMap以this作為key
在構(gòu)造ThreadLocalMap時(shí),使用this作為key來(lái)存儲(chǔ)壳贪,那么對(duì)于同一個(gè)ThreadLocal對(duì)象陵珍,如果同一個(gè)Thread中存儲(chǔ)了多個(gè)值,是如何來(lái)區(qū)分存儲(chǔ)的呢违施?
答案就在firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
關(guān)鍵點(diǎn)就在threadLocalHashCode
互纯,它相當(dāng)于一個(gè)ThreadLocal的ID,實(shí)現(xiàn)的邏輯如下
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
這里用到了一個(gè)非常完美的散列算法磕蒲,可以簡(jiǎn)單理解為留潦,對(duì)于同一個(gè)ThreadLocal下的多個(gè)線程來(lái)說(shuō),當(dāng)任意線程調(diào)用set方法存入一個(gè)數(shù)據(jù)到Entry中的時(shí)候辣往,其實(shí)會(huì)根據(jù)threadLocalHashCode
生成一個(gè)唯一的id標(biāo)識(shí)對(duì)應(yīng)這個(gè)數(shù)據(jù)兔院,存儲(chǔ)在Entry數(shù)據(jù)下標(biāo)中。
-
threadLocalHashCode
是通過(guò)nextHashCode.getAndAdd(HASH_INCREMENT)來(lái)實(shí)現(xiàn)的
i*HASH_INCREMENT+HASH_INCREMENT
,每次新增一個(gè)元素(ThreadLocal)到Entry[],都會(huì)自增0x61c88647,目的為了讓哈希碼能均勻的分布在2的N次方的數(shù)組里 - Entry[i]= hashCode & (length-1)
魔數(shù)0x61c88647
從上面的分析可以看出站削,它是在上一個(gè)被構(gòu)造出的ThreadLocal的threadLocalHashCode的基礎(chǔ)上加上一個(gè)魔數(shù)0x61c88647坊萝。我們來(lái)做一個(gè)實(shí)驗(yàn),看看這個(gè)散列算法的運(yùn)算結(jié)果
private static final int HASH_INCREMENT = 0x61c88647;
public static void main(String[] args) {
magicHash(16); //初始大小16
magicHash(32); //擴(kuò)容一倍
}
private static void magicHash(int size){
int hashCode = 0;
for(int i=0;i<size;i++){
hashCode = i*HASH_INCREMENT+HASH_INCREMENT;
System.out.print((hashCode & (size-1))+" ");
}
System.out.println();
}
輸出結(jié)果
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0
根據(jù)運(yùn)行結(jié)果,這個(gè)算法在長(zhǎng)度為2的N次方的數(shù)組上十偶,確實(shí)可以完美散列菩鲜,沒(méi)有任何沖突, 是不是很神奇。
魔數(shù)0x61c88647的選取和斐波那契散列有關(guān)惦积,0x61c88647對(duì)應(yīng)的十進(jìn)制為1640531527接校。而斐波那契散列的乘數(shù)可以用
(long) ((1L << 31) * (Math.sqrt(5) - 1));
如果把這個(gè)值給轉(zhuǎn)為帶符號(hào)的int,則會(huì)得到-1640531527狮崩。也就是說(shuō)
(long) ((1L << 31) * (Math.sqrt(5) - 1));
得到的結(jié)果就是1640531527蛛勉,也就是魔數(shù)0x61c88647
//(根號(hào)5-1)*2的31次方=(根號(hào)5-1)/2 *2的32次方=黃金分割數(shù)*2的32次方
long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));
System.out.println("32位無(wú)符號(hào)整數(shù): " + l1);
int i1 = (int) l1;
System.out.println("32位有符號(hào)整數(shù): " + i1);
總結(jié),我們用0x61c88647作為魔數(shù)累加為每個(gè)ThreadLocal分配各自的ID也就是threadLocalHashCode再與2的冪取模厉亏,得到的結(jié)果分布很均勻董习。
圖形分析
為了更直觀的體現(xiàn)set
方法的實(shí)現(xiàn),通過(guò)一個(gè)圖形表示如下
set剩余源碼分析
前面分析了set方法第一次初始化ThreadLocalMap的過(guò)程爱只,也對(duì)ThreadLocalMap的結(jié)構(gòu)有了一個(gè)全面的了解。那么接下來(lái)看一下map不為空時(shí)的執(zhí)行邏輯
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 根據(jù)哈希碼和數(shù)組長(zhǎng)度求元素放置的位置招刹,即數(shù)組下標(biāo)
int i = key.threadLocalHashCode & (len-1);
//從i開(kāi)始往后一直遍歷到數(shù)組最后一個(gè)Entry(線性探索)
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key相等恬试,覆蓋value
if (k == key) {
e.value = value;
return;
}
//如果key為null,用新key、value覆蓋疯暑,同時(shí)清理歷史key=null的陳舊數(shù)據(jù)
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//如果超過(guò)閥值训柴,就需要擴(kuò)容了
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
主要邏輯
- 根據(jù)key的散列哈希計(jì)算Entry的數(shù)組下標(biāo)
- 通過(guò)線性探索探測(cè)從i開(kāi)始往后一直遍歷到數(shù)組的最后一個(gè)Entry
- 如果map中的key和傳入的key相等,表示該數(shù)據(jù)已經(jīng)存在妇拯,直接覆蓋
- 如果map中的key為空幻馁,則用新的key、value覆蓋越锈,并清理key=null的數(shù)據(jù)
- rehash擴(kuò)容
replaceStaleEntry
由于Entry的key為弱引用仗嗦,如果key為空,說(shuō)明ThreadLocal這個(gè)對(duì)象被GC回收了甘凭。
replaceStaleEntry
的作用就是把陳舊的Entry進(jìn)行替換
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//向前掃描稀拐,查找最前一個(gè)無(wú)效的slot
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
//通過(guò)循環(huán)遍歷,可以定位到最前面一個(gè)無(wú)效的slot
slotToExpunge = i;
//從i開(kāi)始往后一直遍歷到數(shù)組最后一個(gè)Entry(線性探索)
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//找到匹配的key以后
if (k == key) {
e.value = value;//更新對(duì)應(yīng)slot的value值
//與無(wú)效的sloat進(jìn)行交換
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//如果最早的一個(gè)無(wú)效的slot和當(dāng)前的staleSlot相等丹弱,則從i作為清理的起點(diǎn)
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//從slotToExpunge開(kāi)始做一次連續(xù)的清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//如果當(dāng)前的slot已經(jīng)無(wú)效德撬,并且向前掃描過(guò)程中沒(méi)有無(wú)效slot,則更新slotToExpunge為當(dāng)前位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果key對(duì)應(yīng)的value在entry中不存在躲胳,則直接放一個(gè)新的entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//如果有任何一個(gè)無(wú)效的slot蜓洪,則做一次清理
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
cleanSomeSlots
這個(gè)函數(shù)有兩處地方會(huì)被調(diào)用,用于清理無(wú)效的Entry
- 插入的時(shí)候可能會(huì)被調(diào)用
- 替換無(wú)效slot的時(shí)候可能會(huì)被調(diào)用
區(qū)別是前者傳入的n為元素個(gè)數(shù)坯苹,后者為table的容量
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
// i在任何情況下自己都不會(huì)是一個(gè)無(wú)效slot隆檀,所以從下一個(gè)開(kāi)始判斷
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;// 擴(kuò)大掃描控制因子
removed = true;
i = expungeStaleEntry(i); // 清理一個(gè)連續(xù)段
}
} while ( (n >>>= 1) != 0);
return removed;
}
expungeStaleEntry
執(zhí)行一次全量清理
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;//刪除value
tab[staleSlot] = null;//刪除entry
size--; //map的size遞減
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);// 遍歷指定刪除節(jié)點(diǎn),所有后續(xù)節(jié)點(diǎn)
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {//key為null,執(zhí)行刪除操作
e.value = null;
tab[i] = null;
size--;
} else {//key不為null,重新計(jì)算下標(biāo)
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {//如果不在同一個(gè)位置
tab[i] = null;//把老位置的entry置null(刪除)
// 從h開(kāi)始往后遍歷,一直到找到空為止刚操,插入
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
get操作
set的邏輯分析完成以后闸翅,get的源碼分析就很簡(jiǎn)單了
public T get() {
Thread t = Thread.currentThread();
//從當(dāng)前線程中獲取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//查詢當(dāng)前ThreadLocal變量實(shí)例對(duì)應(yīng)的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {//獲取成功,直接返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果map為null,即還沒(méi)有初始化菊霜,走初始化方法
return setInitialValue();
}
setInitialValue
根據(jù)initialValue()
的value初始化ThreadLocalMap
private T setInitialValue() {
T value = initialValue();//protected方法,用戶可以重寫(xiě)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//如果map不為null,把初始化value設(shè)置進(jìn)去
map.set(this, value);
else
//如果map為null,則new一個(gè)map,并把初始化value設(shè)置進(jìn)去
createMap(t, value);
return value;
}
- 從當(dāng)前線程中獲取ThreadLocalMap坚冀,查詢當(dāng)前ThreadLocal變量實(shí)例對(duì)應(yīng)的Entry,如果不為null,獲取value,返回
- 如果map為null,即還沒(méi)有初始化鉴逞,走初始化方法
remove方法
remove的方法比較簡(jiǎn)單记某,從Entry[]中刪除指定的key就行
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();//調(diào)用Entry的clear方法
expungeStaleEntry(i);//清除陳舊數(shù)據(jù)
return;
}
}
}
應(yīng)用場(chǎng)景
ThreadLocal的實(shí)際應(yīng)用場(chǎng)景:
- 比如在線程級(jí)別,維護(hù)session,維護(hù)用戶登錄信息userID(登陸時(shí)插入构捡,多個(gè)地方獲纫耗稀)
- 數(shù)據(jù)庫(kù)的鏈接對(duì)象
Connection
,可以通過(guò)ThreadLocal來(lái)做隔離避免線程安全問(wèn)題
問(wèn)題
ThreadLocal的內(nèi)存泄漏
ThreadLocalMap中Entry的key使用的是ThreadLocal的弱引用勾徽,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用滑凉,當(dāng)系統(tǒng)執(zhí)行GC時(shí),這個(gè)ThreadLocal勢(shì)必會(huì)被回收喘帚,這樣一來(lái)畅姊,ThreadLocalMap中就會(huì)出現(xiàn)一個(gè)key為null的Entry,而這個(gè)key=null的Entry是無(wú)法訪問(wèn)的吹由,當(dāng)這個(gè)線程一直沒(méi)有結(jié)束的話若未,那么就會(huì)存在一條強(qiáng)引用鏈
Thread Ref - > Thread -> ThreadLocalMap - > Entry -> value 永遠(yuǎn)無(wú)法回收而造成內(nèi)存泄漏
其實(shí)我們從源碼分析可以看到,ThreadLocalMap是做了防護(hù)措施的
- 首先從ThreadLocal的直接索引位置(通過(guò)ThreadLocal.threadLocalHashCode & (len-1)運(yùn)算得到)獲取Entry e倾鲫,如果e不為null并且key相同則返回e
- 如果e為null或者key不一致則向下一個(gè)位置查詢粗合,如果下一個(gè)位置的key和當(dāng)前需要查詢的key相等,則返回對(duì)應(yīng)的Entry乌昔,否則隙疚,如果key值為null,則擦除該位置的Entry玫荣,否則繼續(xù)向下一個(gè)位置查詢
在這個(gè)過(guò)程中遇到的key為null的Entry都會(huì)被擦除甚淡,那么Entry內(nèi)的value也就沒(méi)有強(qiáng)引用鏈,自然會(huì)被回收捅厂。仔細(xì)研究代碼可以發(fā)現(xiàn)贯卦,set操作也有類(lèi)似的思想,將key為null的這些Entry都刪除焙贷,防止內(nèi)存泄露撵割。
但是這個(gè)設(shè)計(jì)一來(lái)與一個(gè)前提條件,就是調(diào)用get或者set方法辙芍,但是不是所有場(chǎng)景都會(huì)滿足這個(gè)場(chǎng)景的啡彬,所以為了避免這類(lèi)的問(wèn)題羹与,我們可以在合適的位置手動(dòng)調(diào)用ThreadLocal的remove函數(shù)刪除不需要的ThreadLocal,防止出現(xiàn)內(nèi)存泄漏
所以建議的使用方法是
- 將ThreadLocal變量定義成private static的庶灿,這樣的話ThreadLocal的生命周期就更長(zhǎng)纵搁,由于一直存在ThreadLocal的強(qiáng)引用,所以ThreadLocal也就不會(huì)被回收往踢,也就能保證任何時(shí)候都能根據(jù)ThreadLocal的弱引用訪問(wèn)到Entry的value值腾誉,然后remove它,防止內(nèi)存泄露
- 每次使用完ThreadLocal峻呕,都調(diào)用它的remove()方法利职,清除數(shù)據(jù)。