ThreadLocal是什麼
ThreadLocal可以理解爲線程局部變量,當使用 ThreadLocal 維護變量時,ThreadLocal 爲每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本,ThreadLocal保證了各個線程的數據互不干擾。
ThreadLocal原理
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap
每個線程中都有一個ThreadLocalMap
數據結構,當執行set方法時,其值是保存在當前線程的threadLocals
變量中,當執行get方法中,是從當前線程的threadLocals
變量獲取。
也就是說 ThreadLocal 本身並不存儲值,它只是作爲一個 key 來讓線程從 ThreadLocalMap 獲取 value。值得注意的是圖中的虛線,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作爲 Key 的,弱引用的對象在 GC 時會被回收。
在ThreadLoalMap中,也是初始化一個大小16的Entry數組,Entry對象用來保存每一個key-value鍵值對,只不過這裏的key永遠都是ThreadLocal對象。通過ThreadLocal對象的set方法,結果把ThreadLocal對象自己當做key,放進了ThreadLoalMap中。
ThreadLoalMap的Entry是繼承WeakReference,和HashMap很大的區別是,Entry中沒有next字段,所以就不存在鏈表的情況了
Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
hash衝突
每個ThreadLocal對象都有一個hash值threadLocalHashCode
,每初始化一個ThreadLocal對象,hash值就增加一個固定的大小0x61c88647
。
在插入過程中,根據ThreadLocal對象的hash值,定位到table中的位置i,過程如下:
- 如果當前位置是空的,那麼正好,就初始化一個Entry對象放在位置i上;
- 位置i已經有Entry對象,如果這個Entry對象的key正好是即將設置的key,那麼重新設置Entry中的value;
- 位置i的Entry對象,和即將設置的key沒關係,那麼只能找下一個空位置;
這樣的話,在get的時候,也會根據ThreadLocal對象的hash值,定位到table中的位置,然後判斷該位置Entry對象中的key是否和get的key一致,如果不一致,就判斷下一個位置
可以發現,set和get如果衝突嚴重的話,效率很低,因爲ThreadLoalMap是Thread的一個屬性,所以即使在自己的代碼中控制了設置的元素個數,但還是不能控制其它代碼的行爲。
內存泄露
當使用ThreadLocal保存一個value時,會在ThreadLocalMap中的數組插入一個Entry對象,在ThreadLocalMap的實現中,key被保存到了WeakReference對象中。這就導致了一個問題,ThreadLocal在沒有外部強引用時,發生GC時會被回收,如果創建ThreadLocal的線程一直持續運行,那麼這個Entry對象中的value就有可能一直得不到回收,發生內存泄露。
解決:
在調用ThreadLocal的get()、set()可能會清除ThreadLocalMap中key爲null的Entry對象,這樣對應的value就沒有GC Roots可達了,下次GC的時候就可以被回收,當然如果調用remove方法,肯定會刪除對應的Entry對象。
但是這些被動的預防措施並不能保證不會內存泄漏:
- 使用static的ThreadLocal,延長了ThreadLocal的生命週期,可能導致的內存泄漏。
- 分配使用了ThreadLocal又不再調用get(),set(),remove()方法,那麼就會導致內存泄漏。
爲什麼使用弱引用
考慮兩種情況:
- key 使用強引用:
- 引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。
- key 使用弱引用:
- 引用的ThreadLocal的對象被回收了,由於ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。
- value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
由於ThreadLocalMap的生命週期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
ThreadLocal和synchronized 區別:
synchronized:以時間換空間,一個變量,讓不同線程排隊訪問;
ThreadLocal:空間換時間,多個變量,多個線程之間同時訪問不受影響。
ThreadLocal 無法解決共享對象的更新問題:
ThreadLocal建議使用static修飾,這個變量是針對一個線程內所有操作共享的,所以設置成靜態的,所有此類實例共享此靜態變量,也就是說在類加載時分配一塊存儲空間,所有此類的對象都可以操作這個變量。
ThreadLocal 應用場景
- 保存線程上下文信息的,在任意地方可以取
- 線程安全的,避免某些情況下需要考慮線程安全必須同步帶來的性能損失。
主要用於一些connection,session資源的管理,比如SqlSession
sqlsession管理
spring 事務管理 TransactionSynchronizationManager。用ThreadLocal存儲Connection,從而各個DAO可以獲取同一個Connection,可以進行事務回滾,提交等操作。
ThreadLocal 和 FastThreadLocal
有測試說FastThreadLocal是ThreadLocal 3倍的速率;
FastThreadLocal是Netty提供的,在池化內存分配的地方都有涉及到。
FastThreadLocal的使用
-
FastThreadLocal用法上兼容ThreadLocal,
-
使用FastThreadLocal居然不用像ThreadLocal那樣先try ………………… 之後finally進行threadLocal對象.remove();
由於構造FastThreadLocalThread的時候,通過FastThreadLocalRunnable對Runnable對象進行了包裝:
FastThreadLocalRunnable在執行完之後都會調用FastThreadLocal.removeAll();
FastThreadLocal並不是什麼情況都快,FastThreadLocalThread線程纔會快,如果是普通線程還更慢。
-
FastThreadLocal操作元素的時候,使用常量下標在數組中進行定位元素來替代ThreadLocal通過哈希和哈希表,這個改動特別在頻繁使用的時候,效果更加顯著!
-
想要利用上面的特徵,線程必須是FastThreadLocalThread或者其子類,默認DefaultThreadFactory都是使用FastThreadLocalThread的
-
只用在FastThreadLocalThread或者子類的線程使用FastThreadLocal纔會更快,因爲FastThreadLocalThread 定義了屬性threadLocalMap類型是InternalThreadLocalMap。如果普通線程會藉助ThreadLocal。