簡單整理下ThreadLocal的原理,以及它需要注意的內存泄漏。
ThreadLocal原理
ThreadLocal不多介紹,可看作線程內的局部變量(這個比喻很貼切)。我們平時聲明的局部變量的范圍一般是方法內的,而ThreadLocal變量的范圍是整個線程。
我們先來看一段代碼demo:
public class Test {
//可看作線程內聲明的局部變量
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void a(){
//設置線程內局部變量值為1
threadLocal.set("1");
}
public void b(){
//獲取線程內局部變量值
System.out.println(threadLocal.get()); //輸出:1
//設置線程內局部變量值為2
threadLocal.set("2");
//獲取線程內局部變量值
System.out.println(threadLocal.get()); ////輸出:2
}
public static void main(String[] args) {
Test test = new Test();
test.a();
test.b();
}
}
通過方法a()
設置了線程局部變量ThreadLocal
的值,然后再另一個方法b()
中獲取并修改了它。由于調用時方法a()
和b()
都在同一線程中,所以可以成功獲取和修改threadLocal
問題:那ThreadLocal是如何做到,使變量的值的使用范圍是整個線程的呢?
這主要得益于線程Thread
類中的一個成員變量:ThreadLocalMap
。這個Map
的鍵值對是<ThreadLocal,Object>
,key
是ThreadLocal對象,value
是該ThreadLocal對象設置的值。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap
可看作保存著該線程內所有的 “線程局部變量”的集合。每個線程都維護著自己的線程局部變量集合:ThreadLocalMap
:
當我們設置一個ThreadLocal的值myThreadLocal.set("1")
時,其實就是在往該線程的成員變量ThreadLocalMap
中添加myThreadLocal-1
的一個元素:
當我們獲取一個ThreadLocal
的值myThreadLocal.get()
時,其實就是從ThreadLocalMap
中獲取key為myThreadLocal
的entry的值:
內存泄漏
ThreadLocal
使用不當是容易發生內存泄漏的。原因在于,我們設置完ThreadLocal
的值后,該線程如果還在運行,ThreadLocalMap
中該ThreadLocal
所在的Entry不會被回收,一直在內存中存在。即存在這種引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry
為了解決這個問題,ThreadLocalMap
中的key被設置為了弱引用,即一段時間后沒被使用的話,key值將被GC垃圾回收機制回收。而ThreadLocal
的get()
、set()
執行時,會檢查ThreaLocalMap
中key值為null的Entry,將value去除。
但是這仍然沒有完全解決內存泄漏的問題,原因在于,如果該線程的再也沒有執行ThreadLocal
的get()
、set()
方法,則value仍然會一直存在內存中,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
所以,為了避免內存泄漏,我們最好在使用完ThreadLocal
后,手動remove()
掉它