ThreadLocal的set方法和get方法,從set方法開始:
public void set(T value) {
Thread t = Thread.currentThread();//獲取當前線程
ThreadLocalMap map = getMap(t);//獲取線程的局部變量
if (map != null)//判斷map是否存在
map.set(this, value);//set值 key是當前ThreadLocal對象 value是value
else
createMap(t, value);//不然 建立一個map設置值
}
get方法:
public T get() {
Thread t = Thread.currentThread();//獲取當前線程
ThreadLocalMap map = getMap(t);//獲取線程的局部變量map
if (map != null) {//當map存在時
ThreadLocalMap.Entry e = map.getEntry(this);//獲取entry(鍵值對)
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;//返回值
}
}
return setInitialValue();//返回null
}
在瞭解了ThreadLocal的內部實現後,我看到一個問題,那就是這些變量是維護在Thread類內部的,這也意味着只要線程不退出,對象的引用將一直存在,
當線程退出是,Thread類會進行一些清理工做,其中就包括清理ThreadLocalMap.下面是具體實現:在Thread類內部:
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
所以,若是咱們使用線程池,那就意味着當前線程未必會退出,(好比固定大小的線程池,線程老是存在的,) 若是這樣,將一些大大的對象設置到ThreadLocal中,可能會使系統出現內存在泄漏,
此時,你但願及時的GC,最好使用ThreadLocal.remove()方法將這個變量移除,就像咱們習慣性的關閉數據庫連接同樣,若是你肯定不須要這個對象了,那麼就應該告訴虛擬機,把他回收掉,防止內存泄漏,
另一種有趣的狀況是JDK也可能容許你想釋放普通變量同樣釋放ThreadLocal.好比,我麼你有時候爲了加入GC.會特地寫出相似obj=null之類的代碼.若是這麼作,obj所指向的對象就會更容易地垃圾回收器發現,從而加速回收,
同理,若是對於ThreadLocal的變量,咱們也手動將其設置null.好比t1=null.那麼這個ThreadLocal對應的全部線程的局部變量都有可能被回收,咱們寫個小例子來看一看奧祕:
public class ThreadLocalDemo_Gc {
static volatile ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected void finalize() throws Throwable { //重載了finalize() 當對象在GC時,打印信息
System.out.println(this.toString() + " is gc");
}
};
static volatile CountDownLatch cd = new CountDownLatch(10000);//倒計時
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (t1.get() == null) {
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
@Override
protected void finalize() throws Throwable {
System.out.println(this.toString() + " is gc");
}
});
System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");
}
Date t = t1.get().parse("2016-12-19 19:29:" + i % 60);
} catch (ParseException e) {
e.printStackTrace();
} finally {
cd.countDown();//完成 計數器減1
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10000; i++) {
es.execute(new ParseDate(i));
}
cd.await();//等待全部線程 完成準備
System.out.println("mission complete!!");
t1 = null;
System.gc();
System.out.println("first GC complete!!");
t1 = new ThreadLocal<>();
cd = new CountDownLatch(1000);
for (int i = 0; i < 10000; i++) {
es.execute(new ParseDate(i));
}
cd.await();
Thread.sleep(1000);
System.gc();
System.out.println("second GC complete!!");
}
}
輸出結果以下:
在主函數Main中,前後進行了2次任務提交,每次10000個任務,在第一次任務提交後,咱們t1設置爲null 接着進行了一次gc,接着咱們進行了第二次任務提交,完成後在進行一次gc,
注意這些輸出,.咱們發現了當t1被設置爲null時候,第一次gc 回收了.接着提交第二次任務,此次咱們也是建立了10個線程,能夠看到,雖然咱們手動remove()這些對象,可是系統依然有可能回收他們.
要了解這裏的回收機制,咱們須要進一步瞭解Thread.ThreadLocalMap的實現,以前咱們說過,ThreadLocalMap是一個相似HashMap的東西,更精確地說,他更加相似WeakHashMap.
ThreadLocalMap的實現使用了弱引用,弱引用是比強引用弱的多的引用,java在虛擬機回收時,若是發現若引用,就會當即回收,ThreadLocalMap內部由一系列Entry構成,每個entry都是WeakReferenc<ThreadLocal>:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
這裏的參數k就是map的key,v就是Map的value.其中k也就是ThreadLocal實例,做爲弱引用使用(super(k)就是調用了WeakReferenc的構造函數,)所以,索然這裏使用了ThreadLocal做爲map的key,可是實際上,他並非真的持有ThreadLocal的引用,而當THreadLocal的外部引用被回收時,ThreadLocalMap中的key就會變成null.當系統進行ThreadLocalMap清理時,就會天然將這些垃圾數據回收,