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