談談ThreadLocal的設計及不足


用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限制。你看,所有的東西都不是十全十美的,我們掌握那個平衡點就行了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM