您好,登錄后才能下訂單哦!
這篇文章給大家介紹Netty的FastThreadLocal的原理及用法是什么,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
簡而言之,FastThreadLocal 是在 ThreadLocal 實現上的一種變種,相比 ThreadLocal 內部通過將自身 hash 的方式在 hashTable 上定位需要的變量存儲位置,FastThreadLocal 選擇在數組上的一個固定的常量位置來存放線程本地變量,這樣的操作看起來并沒有太大區別,但是相比 ThreadLocal 的確體現了性能上的優勢,尤其是在讀操作頻繁的場景下。
如果想要得到 FastThreadLocal 的速度優勢,必須通過 FastThreadLocalThread 或者其子類的線程,才可以使用,因為這個原因,Netty 的 DefaultThreadFactory,其內部默認線程工廠的 newThread()方法就是直接初始化一個 FastThreadLocalThread ,以便期望在 ThreadLocal 的操作中,得到其性能上帶來的優勢。
protected Thread newThread(Runnable r, String name) { return new FastThreadLocalThread(threadGroup, r, name);}
當需要用到 FastThreadLocal 的時候,想必和 jdk 原生的 ThreadLocal 的 api 類似,都是通過初始化一個新的 FastThreadLocal 之后,通過其 set()方法初始化并放入一個變量作為線程本地變量存儲。
public final void set(V value) { if (value != InternalThreadLocalMap.UNSET) { set(InternalThreadLocalMap.get(), value); } else { remove(); }}
因此,在 FastThreadLocal 的 set()方法中,可以看到,存儲本地線程變量的數據結構是一個 InternalThreadLocalMap。
private InternalThreadLocalMap threadLocalMap;
在 FastThreadLocalThread 中,因為本身 threadLocalMap 就是其中的一個成員,能夠快速得到返回。而其他線程實現,就將面臨沒有這個成員的尷尬,Netty 也給出了相應的兼容。
public static InternalThreadLocalMap get() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return fastGet((FastThreadLocalThread) thread); } else { return slowGet(); }}
InternalThreadLocalMap 的 get()方法中,當前線程如果是 FastThreadLocalThread 或是其子類的實現,變直接返回其 InternalThreadLocalMap 進行操作,但對于不屬于上述條件的線程,Netty 通過 slowGet()的方式,也將返回一個 InternalThreadLocalMap。
private static InternalThreadLocalMap slowGet() { ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; InternalThreadLocalMap ret = slowThreadLocalMap.get(); if (ret == null) { ret = new InternalThreadLocalMap(); slowThreadLocalMap.set(ret); } return ret;}
在 slowGet()方法中,當前線程對應的 InternalThreadLocalMap 會通過原生 jdk 下 ThreadLocal 的方式存儲并通過 ThreadLocal 返回,因此,在這個場景下,使用的還是 jdk 原生的 ThreadLocal,但是只占用了原生 ThreadLocal 下的 Entry[]數組的一個位置,具體的變量還是存放在專門為 FastThreadLocal 服務的 InternalThreadLocalMap 中。
在此,隨著 InternalThreadLocalMap 的得到并返回,針對 FastThreadLocal 的 get 和 set 操作,也將變為操作 InternalThreadLocalMap 來達到目的,FastThreadLocal 性能優越的原因,也在 InternalThreadLocalMap 當中。
static final AtomicInteger nextIndex = new AtomicInteger();Object[] indexedVariables;
InternalThreadlocalMap 主要由以上兩個成員組成,其中 indexedVariables 作為一個 Object[]數組,直接用來存放 FastThreadLocal 對應的 value,每個 FastThreadLocal 對象都會在相應的線程的 ThreadLocalMap 中被分配到對應的 index,而這里的具體下標,則由以上的 nextIndex 成員在每個 FastThreadLocal 初始化的時候分配。
private final int index;public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex();}
每個 FastThreadLocal 在構造方法的過程中,都會通過 InternalThreadlocalMap 的 nextVariableIndex()返回 nextIndex 自加后的結果作為其在 InternalThreadlocalMap 上的下標。后續該 FastThreadLocal 在操作變量的時候可以直接通過該 index 定位到 Object[]數組上的位置。
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
而數組上的下標有一個特殊位,一般在其首位也就是 0 的位置,這個位置在 FastThreadLocal 類被加載的時候作為靜態變量被設置。在這個位置上,存放的是一個 FastThreadLocal 對象集合,每個存放到 InternalThreadlocalMap 中的 FastThreadLocal 都會被保存在首位的集合中。
public static final Object UNSET = new Object();
另外,為了具體區分保存的變量是 null 還是不存在當前變量,InternalThreadLocalMap 中定義了一個為 NULL 的成員變量,以便區分上述情況,在一開始,InternalThreadLocalMap 中的 indexedVariables 數組都是 NULL。
相比 FastThreadLocal 的 set 操作,get 方法的過程與邏輯都要簡單的多,因此此處主要以其 set 方法為主。
public final void set(V value) { if (value != InternalThreadLocalMap.UNSET) { set(InternalThreadLocalMap.get(), value); } else { remove(); }}public final void set(InternalThreadLocalMap threadLocalMap, V value) { if (value != InternalThreadLocalMap.UNSET) { if (threadLocalMap.setIndexedVariable(index, value)) { addToVariablesToRemove(threadLocalMap, this); } } else { remove(threadLocalMap); }}
在其 set()方法中,首先會判斷 set 的值是否是 InternalThreadLocalMap 中的 NULL 對象來判斷是 set 操作還是 remove 操作,如果不是,會通過 InternalThreadLocalMap.get()方法獲取當前線程對應的 InternalThreadLocalMap,獲取的過程在前文已經描述過。之后的主要流程主要分為兩步:
?調用 InternalThreadLocalMap 的 setIndexedVariable()方法,將該 FastThreadLocal 成員在構造方法中獲得到的 InternalThreadLocalMap 上的下標作為入參傳入。
public boolean setIndexedVariable(int index, Object value) { Object[] lookup = indexedVariables; if (index < lookup.length) { Object oldValue = lookup[index]; lookup[index] = value; return oldValue == UNSET; } else { expandIndexedVariableTableAndSet(index, value); return true; }}
在 InternalThreadLocalMap 的 setIndexedVariable()方法過程中,set 的過程并不復雜,找到對應的下標,并將對應的值放到 InternalThreadLocalMap 數組下標對應的位置上即宣告結束。但是,因為 FastThreadLocal 在構造過程中雖然預先獲得了對應的下標,但是實際上的數組大小可能完全還沒有達到相應的大小,就要在此處通過 expandIndexedVariableTableAndSet()方法進行擴容,由于是數組的緣故,只需要擴容后將原來的值復制過去,并將剩余的值用 NULL 對象填滿即可。
?如果上一步 set 成功,通過 addToVariablesToRemove()方法將該 FastThreadLocal 對象放入到 InternalThreadLocalMap 的數組中的首位集合中。在這個集合中,對于 FastThreadLocal 是一個強引用。
這樣,對于 FastThreadLocal 的一次 set 操作即宣告結束。
FastThreadLocal 在具體的定位的過程中,只需要根據在構造方法里獲取得到的具體下標就可以定位到具體的數組位置進行變量的存取,而在 jdk 原生的 ThreadLocal 中,具體位置的下標獲取不僅需要計算 ThreadLocal 的 hash 值,并需要在 hashTable 上根據 key 定位的結果,一旦定位之后的結果上已經存在其他 ThreadLocal 的變量,那么則是通過線性探測法,在 hashTable 上尋找下一個位置進行,相比 FastThreadLocal 定位的過程要復雜的多。
FastThreadLocal 由于采取數組的方式,當面對擴容的時候,只需要將原數組中的內容復制過去,并用 NULL 對象填滿剩余位置即可,而在 ThreadLocal 中,由于 hashTable 的緣故,在擴容后還需要進行一輪 rehash,在這過程中,仍舊存在 hash 沖突的可能。
在 FastThreadLocal 中,遍歷當前線程的所有本地變量,只需要取數組首位的集合即可,不需要遍歷數組上的每一個位置。
在原生的 ThreadLocal 中,由于可能存在 ThreadLocal 被回收,但是當前線程仍舊存活的情況導致 ThreadLocal 對應的本地變量內存泄漏的問題,因此在 ThreadLocal 的每次操作后,都會進行啟發式的內存泄漏檢測,防止這樣的問題產生,但也在每次操作后花費了額外的開銷。
而在 FastThreadLocal 的場景下,由于數組首位的 FastThreadLocal 集合中保持著所有 FastThreadLocal 對象的引用,因此當外部的 FastThreadLocal 的引用被置為 null,該 FastThreadLocal 對象仍舊保持著這個集合的引用,不會被回收掉,只需要在線程當前業務操作后,手動調用 FastThreadLocal 的 removeAll()方法,將會遍歷數組首位集合,回收掉所有 FastThreadLocal 的變量,避免內存泄漏的產生,也減少了原生 ThreadLocal 的啟發式檢測開銷。
private static final class DefaultRunnableDecorator implements Runnable { private final Runnable r; DefaultRunnableDecorator(Runnable r) { this.r = r; } @Override public void run() { try { r.run(); } finally { FastThreadLocal.removeAll(); } }}
在 Netty 的 DefaultThreadFactory 中,每個線程在執行為任務后都會調用 FastThreadLocal 的 removeAll()方法。
關于Netty的FastThreadLocal的原理及用法是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。