類ThreadLocal的使用:
? ?變量值的共享可以使用public static變量的形式,所有的線程都使用同一個public static變量膝宁。如果想實現(xiàn)每一個線程都有自己的共享變量效斑,那就可以使用ThreadLocal類造虏。
? ?類ThreadLocal主要解決的就是每個線程綁定自己的值梯醒,可以將ThreadLocal類比喻成全局存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù)郑原。
ThreadLocal詳解:
ThreadLocal定義了四個方法:
get():返回此線程局部變量的當前線程副本中的值贱枣。
initialValue():返回此線程局部變量的當前線程的“初始值”监署。
remove():移除此線程局部變量當前線程的值。
set(T value):將此線程局部變量的當前線程副本中的值設置為指定值纽哥。
除了這四個方法钠乏,ThreadLocal內(nèi)部還有一個靜態(tài)內(nèi)部類ThreadLocalMap,該內(nèi)部類才是實現(xiàn)線程隔離機制的關(guān)鍵春塌,get()晓避、set()、remove()都是基于該內(nèi)部類操作只壳。ThreadLocalMap提供了一種用鍵值對方式存儲每一個線程的變量副本的方法俏拱,key為當前ThreadLocal對象,value則是對應線程的變量副本吼句。
對于ThreadLocal需要注意的有兩點:
ThreadLocal實例本身是不存儲值锅必,它只是提供了一個在當前線程中找到副本值得key。
是ThreadLocal包含在Thread中惕艳,而不是Thread包含在ThreadLocal中搞隐,有些小伙伴會弄錯他們的關(guān)系。
set方法:
上述代碼可以看出set()方法會先當前線程Thread远搪,之后取出它的成員變量ThreadLocalMap劣纲,如果ThreadLocalMap存在,那么進行KEY/VALUE設置谁鳍,KEY就是ThreadLocal癞季。如果ThreadLocalMap沒有,那么創(chuàng)建一個倘潜。說白了绷柒,當前線程中存在一個Map變量,KEY是ThreadLocal涮因,VALUE是你設置的值辉巡。
可以看出map.set(this,value)中this指代ThreadLocal蕊退,而value就是你設置的值
get方法:
這里其實揭示了ThreadLocalMap里面的數(shù)據(jù)存儲結(jié)構(gòu),從上面的代碼來看憔恳,ThreadLocalMap中存放的就是Entry瓤荔,Entry的KEY就是ThreadLocal,VALUE就是值钥组。
private void set(ThreadLocal key, Object value){?
?ThreadLocal.ThreadLocalMap.Entry[] tab = table;
intlen = tab.length;// 根據(jù) ThreadLocal 的散列值输硝,查找對應元素在數(shù)組中的位置
inti = key.threadLocalHashCode & (len-1);// 采用“線性探測法”,尋找合適位置for(ThreadLocal.ThreadLocalMap.Entry e = tab[i];?
?e !=null;?
?e = tab[i = nextIndex(i, len)]) {?
?ThreadLocal k = e.get();// key 存在程梦,直接覆蓋
if(k == key) { e.value = value;return; }// key == null点把,但是存在值(因為此處的e != null)橘荠,說明之前的ThreadLocal對象已經(jīng)被回收了
if(k ==null) {// 用新元素替換陳舊的元素replaceStaleEntry(key, value, i);return;
?}?}// ThreadLocal對應的key實例不存在也沒有陳舊元素,new 一個tab[i] =newThreadLocal.ThreadLocalMap.Entry(key, value);
intsz = ++size;// cleanSomeSlots 清楚陳舊的Entry(key == null)// 如果沒有清理陳舊的 Entry 并且數(shù)組中的元素大于了閾值郎逃,則進行 rehashif(!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
這個set()操作和我們在集合了解的put()方式有點兒不一樣哥童,雖然他們都是key-value結(jié)構(gòu),不同在于他們解決散列沖突的方式不同褒翰。集合Map的put()采用的是拉鏈法贮懈,而ThreadLocalMap的set()則是采用開放定址法
除此之外set()和get()方法還有一個重要的作用就是清楚key為null的entry。
ThreadLocal的內(nèi)存泄漏問題:
1.ThreadLocal為什么會存在內(nèi)存泄漏問題优训?
再來看一下這個圖
ThreadLocalMap使用ThreadLocal的弱引用作為key朵你,如果一個ThreadLocal沒有外部強引用來引用它,那么系統(tǒng) GC 的時候揣非,這個ThreadLocal勢必會被回收抡医,這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry早敬,就沒有辦法訪問這些key為null的Entry的value忌傻,如果當前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收搁嗓,造成內(nèi)存泄漏芯勘。
其實,ThreadLocalMap的設計中已經(jīng)考慮到這種情況腺逛,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value荷愕。
2.那么使用了弱引用后還會存在?
a>使用static的ThreadLocal棍矛,延長了ThreadLocal的生命周期安疗,可能導致的內(nèi)存泄漏
b>分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法,那么就會導致內(nèi)存泄漏够委。
3.為什么使用弱引用荐类?
弱引用是對一個對象(稱為referent)的引用的持有者。使用弱引用后茁帽,可以維持對 referent 的引用玉罐,而不會阻止它被垃圾收集。當垃圾收集器跟蹤堆的時候潘拨,如果對一個對象的引用只有弱引用吊输,那么這個 referent 就會成為垃圾收集的候選對象,就像沒有任何剩余的引用一樣铁追,而且所有剩余的弱引用都被清除季蚂。
? ?下面我們分兩種情況討論:
a>key 使用強引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除扭屁,ThreadLocal不會被回收算谈,導致Entry內(nèi)存泄漏。
b>key 使用弱引用:引用的ThreadLocal的對象被回收了料滥,由于ThreadLocalMap持有ThreadLocal的弱引用然眼,即使沒有手動刪除,ThreadLocal也會被回收幔欧。value在下一次ThreadLocalMap調(diào)用set,get罪治,remove的時候會被清除。
比較兩種情況礁蔗,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長觉义,如果都沒有手動刪除對應key,都會導致內(nèi)存泄漏浴井,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內(nèi)存泄漏晒骇,對應的value在下一次ThreadLocalMap調(diào)用set,get,remove的時候會被清除。
因此磺浙,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長洪囤,如果沒有手動刪除對應key就會導致內(nèi)存泄漏,而不是因為弱引用撕氧。