用Java語言開發的同學對 ThreadLocal
應該都不會陌生,這個類的使用場景很多,特別是在一些框架中經常用到,比如數據庫事務操作,還有MVC框架中數據跨層傳遞。這里我們簡要探討下 ThreadLocal
的內部實現及可能存在的問題。
首先問自己一個問題,讓自己實現一個這個的功能類的話怎么去做?第一反應就是簡單構造一個 Map<Thread, T>
數據結構,key是 Thread
,value就是我們要保存的線程變量 T
。我們看下這種設計有哪些問題:
- 隨着運行時間越久,存在Map里的Thread越多,當Thread退出時,資源也沒有釋放,存在內存泄漏問題
- Map數據因為會被多線程訪問,存在資源競爭,所以還必需對Map做同步安全操作,效率低下
JDK中的 ThreadLocal
精妙的設計來解決問題上述兩個問題。首先每個Thread(線程)內部都有一個Map結構數據ThreadLocalMap<ThreadLocal, T>
,當我們對線程變量賦值時ThreadLocal.set(T value)
時,其實是先獲取當前線程Thread.currentThread())
的內部屬性字段ThreadLocalMap
,然后以當前ThreadLocal
為key設置線程變量值T。這種設計的精髓是,每個Thread
線程都維護一份自己的ThreadLocalMap
數據結構,這樣就解決了上面所述問題中的第二個,不存在競爭條件。
既然每個Thread
內部都維護一個ThreadLocalMap
字典數據結構,字典的Key值是ThreadLocal
,那么當某個ThreadLocal
對象不再使用(沒有其它地方再引用)時,每個已經關聯了此ThreadLocal
的線程怎么在其內部的ThreadLocalMap
里做清除此資源呢?JDK中的ThreadLocalMap
又做了一次精彩的表演,它沒有繼承java.util.Map
類,而是自己實現了一套專門用來定時清理無效資源的字典結構。其內部存儲實體結構Entry<ThreadLocal, T>
繼承自java.lan.ref.WeakReference
,這樣當ThreadLocal
不再被引用時,因為弱引用機制原因,當jvm發現內存不足時,會自動回收弱引用指向的實例內存,即其線程內部的ThreadLocalMap
會釋放其對ThreadLocal
的引用從而讓jvm回收ThreadLocal
對象。這里是重點強調下,是回收對ThreadLocal
對象,而非整個Entry
,所以線程變量中的值T
對象還是在內存中存在的,所以內存泄漏的問題還沒有完全解決。接着分析JDK的實現,會發現在調用ThreadLocal.get()
或者ThreadLocal.set(T)
時都會定期執行回收無效的Entry
操作。所以這就解決了上述問題中的第1個問題。
問題真的都解決了嗎,好像都解決了。因為即沒有競爭資源操作,也不會存在內存泄漏。但是細想一下,總感覺哪里不對勁,真的不會存在內存溢出(OOM)問題嗎?上面一段的分析中,強調ThreadLocalMap
會定期清理內部的無效Entry
對象,觸發的條件就是對TrheadLocal
執行 set,get,remove()等操作時會觸發,但是如果存在這樣的場景,當我們在某個線程上下文中執行ThreadLocal.set(T)
設置了一個很大內在的數據結構,然后該ThreadLocal
被清除引用回收,之前的線程又一直存活着,則這個大內存數據對象T
是一直不回收的,這里很容易寫個代碼測試出OOM來。怎么解決這個問題呢?
Lucene中的org.apache.lucene.util.CloseableThreadLocal
類解決了上述特殊場景引起的問題:即解決JDK中因為定期才執行無效對象回收的問題。CloseableThreadLocal
在內部維護了一個ThreadLocal
,當執行CloseableThreadLocal.set(T)
時,內部其實只是代理的把值賦給內部的ThreadLocal
對象,即執行ThreadLocal.set(new WeakReference(T))
。看到這里應該明白了,這里不是直接存儲T
,則是包裝成弱引用對象,目的就是當內存不足時,jvm可以回收此對象。但是細心的你會發現會引入一個新的問題,即當前線程還存活着的時候,因為內存不足而回收了弱引用對象,這樣會在下次調用get()
時取不到值返回null,這是不可接受的。所以CloseableThreadLocal
在內部還創建了一個數據,WeakHashMap<Thread, T>
,當線程只要存活時,則T就至少有一個引用存在,所以不會被提前回收。但是又引入的第2個問題,對WeakHashMap
的操作要做同步synchronized
限制。你看,所有的東西都不是十全十美的,我們掌握那個平衡點就行了。