FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

Java技術(shù)棧
www.javastack.cn
關(guān)注閱讀更多優(yōu)質(zhì)文章
ThreadLocal 大家都知道是線程本地變量,今天棧長(zhǎng)再介紹一個(gè)神器:FastThreadLocal,從字面上看就是:Fast + ThreadLocal,一個(gè)快的 ThreadLocal?這到底是什么鬼呢?
一、FastThreadLocal 簡(jiǎn)介
FastThreadLocal 并不是 JDK 自帶的,而是在 Netty 中造的一個(gè)輪子,Netty 為什么要重復(fù)造輪子呢?
來看下它源碼中的注釋定義:
/**
?*?A?special?variant?of?{@link?ThreadLocal}?that?yields?higher?access?performance?when?accessed?from?a
?*?{@link?FastThreadLocalThread}.
?*?
?*?Internally,?a?{@link?FastThreadLocal}?uses?a?constant?index?in?an?array,?instead?of?using?hash?code?and?hash?table,
?*?to?look?for?a?variable.??Although?seemingly?very?subtle,?it?yields?slight?performance?advantage?over?using?a?hash
?*?table,?and?it?is?useful?when?accessed?frequently.
?*?
?*?To?take?advantage?of?this?thread-local?variable,?your?thread?must?be?a?{@link?FastThreadLocalThread}?or?its?subtype.
?*?By?default,?all?threads?created?by?{@link?DefaultThreadFactory}?are?{@link?FastThreadLocalThread}?due?to?this?reason.
?*?
?*?Note?that?the?fast?path?is?only?possible?on?threads?that?extend?{@link?FastThreadLocalThread},?because?it?requires
?*?a?special?field?to?store?the?necessary?state.??An?access?by?any?other?kind?of?thread?falls?back?to?a?regular
?*?{@link?ThreadLocal}.
?*?
?*
?*?@param??the?type?of?the?thread-local?variable
?*?@see?ThreadLocal
?*/
public?class?FastThreadLocal<V>?{
?...
}
FastThreadLocal 是一個(gè)特殊的 ThreadLocal 變體,當(dāng)從線程類 FastThreadLocalThread 中訪問 FastThreadLocalm時(shí)可以獲得更高的訪問性能。如果你還不知道什么是 ThreadLocal,可以關(guān)注公眾號(hào)Java技術(shù)棧閱讀我之前分享的文章。
二、FastThreadLocal 為什么快?
在 FastThreadLocal 內(nèi)部,使用了索引常量代替了 Hash Code 和哈希表,源代碼如下:
private?final?int?index;
public?FastThreadLocal()?{
????index?=?InternalThreadLocalMap.nextVariableIndex();
}
public?static?int?nextVariableIndex()?{
????int?index?=?nextIndex.getAndIncrement();
????if?(index?0)?{
????????nextIndex.decrementAndGet();
????????throw?new?IllegalStateException("too?many?thread-local?indexed?variables");
????}
????return?index;
}
FastThreadLocal 內(nèi)部維護(hù)了一個(gè)索引常量 index,該常量在每次創(chuàng)建 FastThreadLocal 中都會(huì)自動(dòng)+1,從而保證了下標(biāo)的不重復(fù)性。
這要做雖然會(huì)產(chǎn)生大量的 index,但避免了在 ThreadLocal ?中計(jì)算索引下標(biāo)位置以及處理 hash 沖突帶來的損耗,所以在操作數(shù)組時(shí)使用固定下標(biāo)要比使用計(jì)算哈希下標(biāo)有一定的性能優(yōu)勢(shì),特別是在頻繁使用時(shí)會(huì)非常顯著,用空間換時(shí)間,這就是高性能 Netty 的巧妙之處。
要利用 FastThreadLocal 帶來的性能優(yōu)勢(shì),就必須結(jié)合使用 FastThreadLocalThread 線程類或其子類,因?yàn)?FastThreadLocalThread 線程類會(huì)存儲(chǔ)必要的狀態(tài),如果使用了非 FastThreadLocalThread 線程類則會(huì)回到常規(guī) ThreadLocal。
Netty 提供了繼承類和實(shí)現(xiàn)接口的線程類:
- FastThreadLocalRunnable
- FastThreadLocalThread

Netty 也提供了 DefaultThreadFactory 工廠類,所有由 DefaultThreadFactory 工廠類創(chuàng)建的線程默認(rèn)就是 FastThreadLocalThread 類型,來看下它的創(chuàng)建過程:

先創(chuàng)建 FastThreadLocalRunnable,再創(chuàng)建 FastThreadLocalThread,基友搭配,干活不累,一定要配合使用才“快”。
三、FastThreadLocal 實(shí)戰(zhàn)
要使用 FastThreadLocal 就需要導(dǎo)入 Netty 的依賴了:
????io.netty
????netty-all
????4.1.52.Final
寫一個(gè)測(cè)試小示例:
import?io.netty.util.concurrent.DefaultThreadFactory;
import?io.netty.util.concurrent.FastThreadLocal;
public?class?FastThreadLocalTest?{
????public?static?final?int?MAX?=?100000;
????public?static?void?main(String[]?args)?{
????????new?Thread(()?->?threadLocal()).start();
????????new?Thread(()?->?fastThreadLocal()).start();
????}
????private?static?void?fastThreadLocal()?{
????????long?start?=?System.currentTimeMillis();
????????DefaultThreadFactory?defaultThreadFactory?=?new?DefaultThreadFactory(FastThreadLocalTest.class);
????????FastThreadLocal[]?fastThreadLocal?=?new?FastThreadLocal[MAX];
????????for?(int?i?=?0;?i?????????????fastThreadLocal[i]?=?new?FastThreadLocal<>();
????????}
????????Thread?thread?=?defaultThreadFactory.newThread(()?->?{
????????????for?(int?i?=?0;?i?????????????????fastThreadLocal[i].set("java:?"?+?i);
????????????}
????????????System.out.println("fastThreadLocal?set:?"?+?(System.currentTimeMillis()?-?start));
????????????for?(int?i?=?0;?i?????????????????for?(int?j?=?0;?j?????????????????????fastThreadLocal[i].get();
????????????????}
????????????}
????????});
????????thread.start();
????????try?{
????????????thread.join();
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????System.out.println("fastThreadLocal?total:?"?+?(System.currentTimeMillis()?-?start));
????}
????private?static?void?threadLocal()?{
????????long?start?=?System.currentTimeMillis();
????????ThreadLocal[]?threadLocals?=?new?ThreadLocal[MAX];
????????for?(int?i?=?0;?i?????????????threadLocals[i]?=?new?ThreadLocal<>();
????????}
????????Thread?thread?=?new?Thread(()?->?{
????????????for?(int?i?=?0;?i?????????????????threadLocals[i].set("java:?"?+?i);
????????????}
????????????System.out.println("threadLocal?set:?"?+?(System.currentTimeMillis()?-?start));
????????????for?(int?i?=?0;?i?????????????????for?(int?j?=?0;?j?????????????????????threadLocals[i].get();
????????????????}
????????????}
????????});
????????thread.start();
????????try?{
????????????thread.join();
????????}?catch?(InterruptedException?e)?{
????????????e.printStackTrace();
????????}
????????System.out.println("threadLocal?total:?"?+?(System.currentTimeMillis()?-?start));
????}
}
結(jié)果輸出:

可以看出,在大量讀寫面前,寫操作的效率差不多,但讀操作 FastThreadLocal 比 ThreadLocal 快的不是一個(gè)數(shù)量級(jí),簡(jiǎn)直是秒殺 ThreadLocal 的存在。
當(dāng)我把 MAX 值調(diào)整到 1000 時(shí),結(jié)果輸出:

讀寫操作不多時(shí),ThreadLocal 明顯更勝一籌!
上面的示例是單線程測(cè)試多個(gè) *ThreadLocal,即數(shù)組形式,另外,我也測(cè)試了多線程單個(gè) *ThreadLocal,這時(shí)候 FastThreadLocal 效率就明顯要落后于 ThreadLocal。。
最后需要說明的是,在使用完 FastThreadLocal 之后不用 remove 了,因?yàn)樵?FastThreadLocalRunnable 中已經(jīng)加了移除邏輯,在線程運(yùn)行完時(shí)會(huì)移除全部綁定在當(dāng)前線程上的所有變量。

所以,使用 FastThreadLocal 導(dǎo)致內(nèi)存溢出的概率會(huì)不會(huì)要低于 ThreadLocal?
不一定,因?yàn)?FastThreadLocal 會(huì)產(chǎn)生大量的 index 常量,所謂的空間換時(shí)間,所以感覺 FastThreadLocal 內(nèi)存溢出的概率更大,但好在每次使用完都會(huì)自動(dòng) remove。
四、總結(jié)
Netty 中的 FastThreadLocal 在大量頻繁讀寫操作時(shí)效率要高于 ThreadLocal,但要注意結(jié)合 Netty 自帶的線程類使用,這可能就是 Netty 為什么高性能的奧妙之一吧!
如果沒有大量頻繁讀寫操作的場(chǎng)景,JDK 自帶的 ThreadLocal 足矣,并且性能還要優(yōu)于 FastThreadLocal。
好了,今天的分享就到這里了,覺得有用,轉(zhuǎn)發(fā)分享一下哦。
最后,Java 系列教程還會(huì)繼續(xù)更新,關(guān)注Java技術(shù)棧公眾號(hào)第一時(shí)間推送,還可以在公眾號(hào)菜單中獲取歷史 Java 教程,都是干貨。
版權(quán)申明:本文系公眾號(hào) "Java技術(shù)棧" 原創(chuàng),原創(chuàng)實(shí)屬不易,轉(zhuǎn)載、引用本文內(nèi)容請(qǐng)注明出處,禁止抄襲、洗稿,請(qǐng)自重,尊重他人勞動(dòng)成果和知識(shí)產(chǎn)權(quán)。






關(guān)注Java技術(shù)??锤喔韶?/strong>
戳原文,獲取精選面試題!
