Java中的ThreadLocal類允許我們創建只能被同一個線程讀寫的變量。因此,如果一段代碼含有一個ThreadLocal變量的引用,即使兩個線程同時執行這段代碼,它們也無法訪問到對方的ThreadLocal變量。
在我們日常 Web 開發中難免會遇到需要把一個參數層層的傳遞到最內層的情況,但是中間層可能根本不需要使用這個參數,因此這樣我們完全沒有必要在每一個方法裏面都傳遞這樣一個通用的參數。Java的Web項目大部分都是基於Tomcat,每次訪問都是一個新的線程,這樣讓我們聯想到了,每一個線程都獨享一個ThreadLocal,在接收請求的時候set特定內容,在需要的時候get這個值。例如,登錄信息就可以這麼存儲。
ThreadLocal<Map> userInfo = new ThreadLocal<Map>();
但是在獲取userInfo的過程中,卻發現它時有時無,甚至會有其他用戶的登錄信息。
時有時無,是因爲頁面首次跳轉到這個頁面時,因爲cookie中並無登錄信息,攔截器也就獲取不到登錄信息,因此賦值爲空。
有其他用戶的登錄信息,則是因爲對登錄信息賦值時,會先判斷一下,對象是否有值,如果有值,則並不覆蓋原值,或者是因爲並未走對此賦值的攔截器,總之產生了內存泄漏的問題。
每次的請求都會有一個Thread,每個 Thread都會擁有一個 ThreadLocalMap變量,來存放屬於該 Thread的所有 ThreadLocal變量。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Map中的key爲一個threadlocal實例.
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
}
這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置爲null以後,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因爲存在一條從current thread連接過來的強引用. 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收。
只要這個線程對象被gc回收,就不會出現內存泄露,但在使用線程池的時候,請求結束後線程並不會被銷燬的,會再次複用因此就出現了內存泄露。因此建議在ThreadLocal業務週期處理完成時,最好顯示的調用remove()方法,清空“線程局部變量”中的值,或者在業務週期開始時,重新賦值。