類ThreadLocal的使用:
? ?變量值的共享可以使用public static變量的形式,所有的線程都使用同一個public static變量。如果想實現每一個線程都有自己的共享變量,那就可以使用ThreadLocal類。
? ?類ThreadLocal主要解決的就是每個線程綁定自己的值,可以將ThreadLocal類比喻成全局存放數據的盒子,盒子中可以存儲每個線程的私有數據。
ThreadLocal詳解:
ThreadLocal定義了四個方法:
get():返回此線程局部變量的當前線程副本中的值。
initialValue():返回此線程局部變量的當前線程的“初始值”。
remove():移除此線程局部變量當前線程的值。
set(T value):將此線程局部變量的當前線程副本中的值設置為指定值。
除了這四個方法,ThreadLocal內部還有一個靜態內部類ThreadLocalMap,該內部類才是實現線程隔離機制的關鍵,get()、set()、remove()都是基于該內部類操作。ThreadLocalMap提供了一種用鍵值對方式存儲每一個線程的變量副本的方法,key為當前ThreadLocal對象,value則是對應線程的變量副本。
對于ThreadLocal需要注意的有兩點:
ThreadLocal實例本身是不存儲值,它只是提供了一個在當前線程中找到副本值得key。
是ThreadLocal包含在Thread中,而不是Thread包含在ThreadLocal中,有些小伙伴會弄錯他們的關系。
set方法:
上述代碼可以看出set()方法會先當前線程Thread,之后取出它的成員變量ThreadLocalMap,如果ThreadLocalMap存在,那么進行KEY/VALUE設置,KEY就是ThreadLocal。如果ThreadLocalMap沒有,那么創建一個。說白了,當前線程中存在一個Map變量,KEY是ThreadLocal,VALUE是你設置的值。
可以看出map.set(this,value)中this指代ThreadLocal,而value就是你設置的值
get方法:
這里其實揭示了ThreadLocalMap里面的數據存儲結構,從上面的代碼來看,ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。
private void set(ThreadLocal key, Object value){?
?ThreadLocal.ThreadLocalMap.Entry[] tab = table;
intlen = tab.length;// 根據 ThreadLocal 的散列值,查找對應元素在數組中的位置
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對象已經被回收了
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 并且數組中的元素大于了閾值,則進行 rehashif(!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
這個set()操作和我們在集合了解的put()方式有點兒不一樣,雖然他們都是key-value結構,不同在于他們解決散列沖突的方式不同。集合Map的put()采用的是拉鏈法,而ThreadLocalMap的set()則是采用開放定址法
除此之外set()和get()方法還有一個重要的作用就是清楚key為null的entry。
ThreadLocal的內存泄漏問題:
1.ThreadLocal為什么會存在內存泄漏問題?
再來看一下這個圖
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用來引用它,那么系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內存泄漏。
其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。
2.那么使用了弱引用后還會存在?
a>使用static的ThreadLocal,延長了ThreadLocal的生命周期,可能導致的內存泄漏
b>分配使用了ThreadLocal又不再調用get(),set(),remove()方法,那么就會導致內存泄漏。
3.為什么使用弱引用?
弱引用是對一個對象(稱為referent)的引用的持有者。使用弱引用后,可以維持對 referent 的引用,而不會阻止它被垃圾收集。當垃圾收集器跟蹤堆的時候,如果對一個對象的引用只有弱引用,那么這個 referent 就會成為垃圾收集的候選對象,就像沒有任何剩余的引用一樣,而且所有剩余的弱引用都被清除。
? ?下面我們分兩種情況討論:
a>key 使用強引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。
b>key 使用弱引用:引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
比較兩種情況,我們可以發現:由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
因此,ThreadLocal內存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應key就會導致內存泄漏,而不是因為弱引用。