FastThreadLocal 從字面意義上來看,它是“Fast”+“ThreadLocal”的結(jié)合體,寓意為快速的 ThreadLocal。那么,問題來了,Netty 為什么要再造一個 FastThreadLocal?FastThreadLocal 運行快的原因是啥?除了快之外,它還有其他優(yōu)勢嗎?
ThreadLocal 線程本地變量,每個線程都擁有一份該變量的獨立副本,即使是在多線程環(huán)境下,每個線程也只能修改和訪問自己的那份副本,從而避免了線程安全問題,實現(xiàn)了線程間的隔離。
ThreadLocal 底層是使用 ThreadLocalMap 實現(xiàn)的,這點從 JDK 的源碼中可以看出,核心源碼如下:
private void set(Thread t, T value) { ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); }}
從 ThreadLocal 的 set 方法可以看出,ThreadLocal 是存儲在 ThreadLocalMap 中的,咱們繼續(xù)看 ThreadLocalMap 的源碼實現(xiàn):
從上面源碼可以看出,ThreadLocalMap 中存放的是 Entry(哈希桶),而 Entry 中的 key 就是 ThreadLocal,而 value 則是要存儲的值,所以我們得出 ThreadLocal 的底層實現(xiàn)如下:
因為 ThreadLocal 底層是使用 ThreadLocalMap 實現(xiàn)的,ThreadLocalMap 類似于哈希表。當(dāng)一個線程是擁有多個 ThreadLocal 時,ThreadLocalMap 很容易發(fā)生 Hash 沖突,此時 ThreadLocal 就不得不使用線性探測法來解決哈希沖突了,而在解決 Hash 沖突時需要不停地向下尋找,效率較低,因此 ThreadLocal 存在的第一個問題就是性能較低。
ThreadLocal 也存在內(nèi)存泄漏的問題,具體來說 ThreadLocalMap 使用 ThreadLocal 對象作為鍵(Key),并且這個鍵是弱引用(WeakReference)類型。這意味著當(dāng)沒有其他強(qiáng)引用指向 ThreadLocal 對象時,它將會在下次垃圾回收時被回收。然而,Entry 中保存的值(Value)仍然是強(qiáng)引用,這就可能導(dǎo)致以下問題:
所以,綜合來看,在使用 ThreadLocal 時,如果在使用完之后,未及時調(diào)用 remove() 方法的話,就會出現(xiàn)內(nèi)存泄漏的問題。
為了解決 ThreadLocal 存在的這些問題,所以 Netty 創(chuàng)造出了一個 FastThreadLocal,F(xiàn)astThreadLocal 的特點如下。
FastThreadLocal 之所以性能高的原因是因為其存儲結(jié)構(gòu),在 FastThreadLocal 中并沒有向 ThreadLocal 那樣,使用哈希表來存儲元素,而是使用了數(shù)組來進(jìn)行元素存儲,它的核心實現(xiàn)源碼如下:
public class FastThreadLocal<V> { // FastThreadLocal中的index是記錄了該它維護(hù)的數(shù)據(jù)應(yīng)該存儲的位置 // InternalThreadLocalMap數(shù)組中的下標(biāo), 它是在構(gòu)造函數(shù)中確定的 private final int index; public InternalThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); } // 省略其他代碼}
FastThreadLocal 核心類 InternalThreadLocalMap 的實現(xiàn)源碼如下:
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap { // 自增索引, ?于計算下次存儲到Object數(shù)組中的位置 private static final AtomicInteger nextIndex = new AtomicInteger(); private static final int ARRAY_LIST_CAPACITY_MAX_SIZE = Integer.MAX_VALUE - 8; public static int nextVariableIndex() { int index = nextIndex.getAndIncrement(); if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) { nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE); throw new IllegalStateException("too many thread-local indexed variables"); } return index; } // 省略其他代碼}
從上述源碼可以看出,F(xiàn)astThreadLocal 在初始化的時候分配一個數(shù)組索引 index,index 的值采用原子類 AtomicInteger 保證順序遞增,通過調(diào)用 InternalThreadLocalMap.nextVariableIndex() 方法獲得。然后在讀寫數(shù)據(jù)的時候通過數(shù)組下標(biāo) index 直接定位到 FastThreadLocal 的位置,時間復(fù)雜度為 O(1)。如果數(shù)組下標(biāo)遞增到非常大,那么數(shù)組也會比較大,所以 FastThreadLocal 是通過空間換時間的思想提升讀寫性能。
因此,在 FastThreadLocal 中并不需要使用線性探測法來解決 Hash 沖突,因為它是使用數(shù)組進(jìn)行存儲的,每次使用下標(biāo)進(jìn)行查詢即可,它的查詢時間復(fù)雜度也是 O(1) 的,所以它的操作效率很高。
JDK 原生的 ThreadLocal 使用不當(dāng)可能造成內(nèi)存泄漏,只能等待線程銷毀。然而 FastThreadLocal 卻不存在這個問題,在 FastThreadLocal 中不僅提供了 remove() 方法可以主動清除對象,而且它還封裝了 FastThreadLocalRunnable,F(xiàn)astThreadLocalRunnable 在最后使用完之后會自動調(diào)用 removeAll() 方法將集合中所有對象清理掉,因此 FastThreadLocal 更安全。
FastThreadLocalRunnable 自動清除對象的實現(xiàn)核心源碼如下:
final class FastThreadLocalRunnable implements Runnable { private final Runnable runnable; @Override public void run() { try { runnable.run(); } finally { FastThreadLocal.removeAll(); } } static Runnable wrap(Runnable runnable) { return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable); }}
FastThreadLocal 相比于 ThreadLocal 存在以下兩個主要優(yōu)點:
本文鏈接:http://www.tebozhan.com/showinfo-26-91377-0.html京東二面:為什么Netty要創(chuàng)造FastThreadLocal?
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com