ThreadLocal是什么
提供線程局部變量囊榜,一個線程的局部變量在多個線程中有獨立的副本审胸,特點有:簡單(開箱即用),快速(無額外開銷)卸勺,安全(線程安全)砂沛;場景:多線程場景(資源持有,并發(fā)計算曙求,線程一致性碍庵,線程安全)使用hash表實現(xiàn),幾乎所有提供多線程特征的語言都是其應(yīng)用范圍悟狱。
ThreadLocal基本的API:帶有泛型的構(gòu)造函數(shù)静浴,訪問器get/set,初始化,回收挤渐。
ThreadLocal原理
ThreadLocal苹享,連接ThreadLocalMap和Thread。來處理Thread的TheadLocalMap屬性浴麻,包括init初始化屬性賦值得问、get對應(yīng)的變量,set設(shè)置變量等软免。通過當(dāng)前線程宫纬,獲取線程上的ThreadLocalMap屬性,對數(shù)據(jù)進行g(shù)et膏萧、set等操作漓骚。ThreadLocalMap蝌衔,用來存儲數(shù)據(jù),采用類似hashmap機制蝌蹂,存儲了以threadLocal為key噩斟,需要隔離的數(shù)據(jù)為value的Entry鍵值對數(shù)組結(jié)構(gòu)。ThreadLocal叉信,有個ThreadLocalMap類型的屬性亩冬,存儲的數(shù)據(jù)就放在這兒。
ThreadLocalMap是ThreadLocal內(nèi)部類硼身,由ThreadLocal創(chuàng)建,Thread有ThreadLocal.ThreadLocalMap類型的屬性覆享。
源碼盤點
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap
這個類在構(gòu)造中創(chuàng)建了一個數(shù)組佳遂, new Entry[INITIAL_CAPACITY]; ,Entry里面就是一個object的對象,然后里面主要getEntry和set方法進行存取和讀取。
private void set(ThreadLocal<?> key, Object value) {
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.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i); // 刪除key為null的Entry
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);//根據(jù)key獲取位下標(biāo)
Entry e = table[i]; // 根據(jù)下標(biāo)撒顿,獲取這個Entry 里面是一個object丑罪,實現(xiàn)了軟引用
if (e != null && e.get() == key)
return e; //校驗沒問題后返回
else
return getEntryAfterMiss(key, i, e);
}
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.refersTo(key)) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
if (e.refersTo(key))
return e;
if (e.refersTo(null)) // 刪除key為null的Entry
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
使用場景
四種核心場景:
1、線程資源持有(ThreadLocalMap實現(xiàn)凤壁,持有線程資源供線程的各個部分使用吩屹,全局獲取減少變成難度)
2、線程資源一致性(幫助需要保持線程一致的資源拧抖,維護一致性煤搜,降低編程難度,例如:JDBC會話連接)
3唧席、線程安全(幫助只考慮了單線程的程序庫擦盾,無遐想多線程場景遷移)
4、分布式計算(幫助分布式計算場景的各個線程累計局部計算結(jié)果)
ThreadLocal內(nèi)存泄漏淌哟,如何避免
內(nèi)存泄漏為程序在申請內(nèi)存后迹卢,無法釋放已申請的內(nèi)存空間,一次泄露危害可以忽略徒仓,但內(nèi)存泄漏堆積后果很嚴(yán)重腐碱,無論多少內(nèi)存,遲早會被占光掉弛。
不再會被使用的對象或者變量占用的內(nèi)存空間不能被回收症见,就是內(nèi)存泄漏。
強引用:使用最普遍的一個引用(new)狰晚,一個對象具有強引用筒饰,不會被垃圾收集器回收。當(dāng)內(nèi)存空間不足壁晒,java虛擬機寧愿OOM瓷们,都不會回收。
如果想取消強引用和某個對象之間的關(guān)聯(lián),可以顯示將對象復(fù)制為null谬晕,這樣jvm就會在安全區(qū)域執(zhí)行g(shù)c進行垃圾回收碘裕。
弱引用:jvm進行垃圾回收時,無論內(nèi)存是否充足攒钳,都會回收被弱引用關(guān)聯(lián)的對象帮孔。在java中,用WeakReference類來表示不撑。
ThreadLocal的實現(xiàn)原理文兢,每一個Thread維護一個ThreadLocalMap,ThreadLocalMap是由一個個Entry構(gòu)成焕檬,而Entry繼承了弱引用姆坚,key為使用弱引用的ThreadLocal對象,value為線程變量的副本实愚。
ThreadLocalMap使用ThreadLocal的弱引用作為key兼呵,如果一個ThreadLocal不存在外部的強引用時,Key(ThreadLocal對象)勢必會被GC回收腊敲,這樣就會導(dǎo)致ThreadLocalMap中的key為null击喂,而value還存在著強引用,只有thread線程退出以后碰辅,value的強引用鏈才會斷掉懂昂,但是如果線程遲遲不結(jié)速的話,這些key為null的Entry的value就會一直存在引用鏈乎赴。
key使用強引用
當(dāng)ThreadLocalMap的key使用強引用時忍法,此時若是外部的ThreadLocal對象被置為null,按理說應(yīng)該被回收榕吼,但是ThreadLocalMap中還持有對ThreadLocal的強引用饿序,如果沒有手動刪除,那么ThreadLocal不會被回收羹蚣,導(dǎo)致Entry內(nèi)存泄漏原探。
key使用弱引用
當(dāng)ThreadLocalMap
的key
為弱引用回收ThreadLocal
對象時,由于ThreadLocalMap
只持有ThreadLocal
的弱引用顽素,即使沒有手動刪除咽弦,也不會影響ThreadLocal的回收。當(dāng)key為null時胁出,在下一個調(diào)用ThreadLocalMap的set
型型、get
、remove
方法時會清除value值全蝶。
ThreadLocal正確使用方法:
- 每次使用完ThreadLocal都調(diào)用它的remove方法清楚數(shù)據(jù)
- 將ThreadLocal變量定義為private static闹蒜,這樣就一直存在ThreadLocal的強引用寺枉,也就能保證在任何時候都能通過ThreadLocal的弱引用訪問到Entry的value值,繼續(xù)清除绷落。