*ThreadLocal 詳解

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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章