ThreadLocal
1.ThreadLocal簡(jiǎn)介
??? ??? 通常情況下,我們的變量可以被任何一個(gè)線程訪問(wèn)并修改。如果想每個(gè)線程都有一個(gè)自己的專屬本地變量該怎么辦呢?ThreadLocal解決了這個(gè)問(wèn)題。
??? ??? ThreadLocal類主要解決的就是讓每一個(gè)線程綁定自己的值,ThreadLocal可以看作是一個(gè)放數(shù)據(jù)的盒子,這個(gè)盒子可以存放線程的私有數(shù)據(jù)。當(dāng)我們?cè)诙嗑€程下使用SimpleDateFormat類的時(shí)候可能出現(xiàn)過(guò)線程安全問(wèn)題(這里不做詳細(xì)介紹),可以使用ThreadLocal來(lái)解決這個(gè)問(wèn)題。
2.ThreadLocal示例
public class ThreadLocalExample implements Runnable { //SimpleDateFormat 不是線程安全的,所以每個(gè)線程都要有??獨(dú)?的副本
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() i> new SimpleDateFormat("yyyyMMdd HHmm"));
public static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){
Thread t = new Thread(obj, ""+i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name="+Thread.currentThread().getName()+" default Formatter ="+formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won'treflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name="+Thread.currentThread().getName()+" formatter "+formatter.get().toPattern());
}
}
運(yùn)行結(jié)果
Thread Name= 0 default Formatter = yyyyMMdd HHmm
Thread Name= 0 formatter = yy-M-d ah:mm
Thread Name= 1 default Formatter = yyyyMMdd HHmm
Thread Name= 2 default Formatter = yyyyMMdd HHmm
Thread Name= 1 formatter = yy-M-d ah:mm
Thread Name= 3 default Formatter = yyyyMMdd HHmm
Thread Name= 2 formatter = yy-M-d ah:mm
Thread Name= 4 default Formatter = yyyyMMdd HHmm
Thread Name= 3 formatter = yy-M-d ah:mm
Thread Name= 4 formatter = yy-M-d ah:mm
Thread Name= 5 default Formatter = yyyyMMdd HHmm
Thread Name= 5 formatter = yy-M-d ah:mm
Thread Name= 6 default Formatter = yyyyMMdd HHmm
Thread Name= 6 formatter = yy-M-d ah:mm
Thread Name= 7 default Formatter = yyyyMMdd HHmm
Thread Name= 7 formatter = yy-M-d ah:mm
Thread Name= 8 default Formatter = yyyyMMdd HHmm
Thread Name= 9 default Formatter = yyyyMMdd HHmm
Thread Name= 8 formatter = yy-M-d ah:mm
Thread Name= 9 formatter = yy-M-d ah:mm
3.ThreadLocal原理
首先看看Thread的一個(gè)源碼
public class Thread implements Runnable{
......
//與該線程有關(guān)的ThreadLocal值
ThreadLocal.ThreadLocalMap threadLocals = null;
//與該線程有關(guān)的InheritableThreadLocal值
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
??? ??? 從這個(gè)源碼可以看出來(lái)Thread類有兩個(gè)變量:threadLocals和inheritableThreadLocals,這兩個(gè)變量都是ThreadLocalMap類型的。默認(rèn)這兩個(gè)變量是null,只有當(dāng)前線程調(diào)用ThreadLocal類的get或set方法的時(shí)候才會(huì)創(chuàng)建他們。調(diào)用get、set方法實(shí)際上是調(diào)用了ThreadLocalMap的get()或set();
public void set(T value){
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null){
map.set(this, value);
}else{
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t){
return t.threadLocals;
}
??? ??? 通過(guò)上面的分析可以得出:變量存放在了ThradLocalMap中,并不在ThreadLocal上,ThreadLoca可以看作是ThreadLocalMap的也給封裝類型。先通過(guò)Thread.currentThread()方法得到當(dāng)前線程對(duì)象,然后調(diào)用ThreadLocalMap的getMap(Thread t)方法得到該線程的ThreadLocalMap對(duì)象
4.ThreadLocal內(nèi)存泄露問(wèn)題
??? ??? ThreadLocalMap中使用的key是ThreadLocal的一個(gè)弱引用,而value是一個(gè)強(qiáng)引用。所以當(dāng)ThradLocal沒有被外部強(qiáng)引用的話,在垃圾回收的時(shí)候會(huì)被清理掉,而value不會(huì)被清理掉。這時(shí)會(huì)出現(xiàn)key為null的Entry。如果我們不做任何措施的話就會(huì)造成內(nèi)存泄露問(wèn)題。解決方法:ThreadLocalMap考慮了這種情況,在調(diào)用get(),set(),remove()方法時(shí)都會(huì)清理掉key為null的記錄。在使用完ThreadLocal方法后最好手動(dòng)調(diào)用remove()方法。
,
,
,
弱引用介紹
??? ??? 如果?個(gè)對(duì)象只具有弱引?,那就類似于可有可?的?活?品。弱引?與軟引?的區(qū)別在于:只具有弱引?的對(duì)象擁有更短暫的?命周期。在垃圾回收器線程掃描它 所管轄的內(nèi)存區(qū)域的過(guò)程中,?旦發(fā)現(xiàn)了只具有弱引?的對(duì)象,不管當(dāng)前內(nèi)存空間?夠與否,都會(huì)回收它的內(nèi)存。不過(guò),由于垃圾回收器是?個(gè)優(yōu)先級(jí)很低的線程, 因此不?定會(huì)很快發(fā)現(xiàn)那些只具有弱引?的對(duì)象。弱引?可以和?個(gè)引?隊(duì)列(ReferenceQueue)聯(lián)合使?,如果弱引?所引?的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引?加?到與之關(guān)聯(lián)的引?隊(duì)列中。
???