維恩圖在線制作網(wǎng)站站長工具的使用seo綜合查詢運(yùn)營
FastThreadLocal 快在哪里 ?
- 引言
- FastThreadLocal
- set
- 如何獲取當(dāng)前線程私有的InternalThreadLocalMap ?
- 如何知道當(dāng)前線程使用到了哪些FastThreadLocal實(shí)例 ?
- get
- 垃圾回收
- 小結(jié)
引言
FastThreadLocal 是 Netty 中造的一個(gè)輪子,那么為什么放著好端端的ThreadLocal不用,卻要重復(fù)造輪子呢?下面是Netty官方在源碼注釋中給出的解釋:
- FastThreadLocal是ThreadLocal的一種特殊變體,當(dāng)從FastThreadLocalThread訪問時(shí)可以獲得更高的訪問性能。
- 內(nèi)部FastThreadLocal使用數(shù)組中的常量索引來查找變量,而不是使用哈希碼和哈希表來查找。盡管看似非常微小,但與使用哈希表相比,它在性能上略有優(yōu)勢(shì),特別是在頻繁訪問時(shí)。
本文我們就來簡(jiǎn)單看看FastThreadLocal的具體實(shí)現(xiàn)。
在正式進(jìn)入實(shí)現(xiàn)解析之前,下面先給出FastThreadLocal使用示例:
private static void fastThreadLocal() {final int MAX = 100000;long start = System.currentTimeMillis();// DefaultThreadFactory是Netty提供的實(shí)現(xiàn),用于創(chuàng)建類型為FastThreadLocalThread的線程DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class);FastThreadLocal<String>[] fastThreadLocal = new FastThreadLocal[MAX];for (int i = 0; i < MAX; i++) {fastThreadLocal[i] = new FastThreadLocal<>();}// 測(cè)試單線程讀寫FastThreadLocal的耗時(shí)Thread thread = defaultThreadFactory.newThread(() -> {for (int i = 0; i < MAX; i++) {fastThreadLocal[i].set("java: " + i);}System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start));for (int i = 0; i < MAX; i++) {for (int j = 0; j < MAX; j++) {fastThreadLocal[i].get();}}});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("fastThreadLocal total: " + (System.currentTimeMillis() - start));}
FastThreadLocal
整體來看,FastThreadLocal的整體結(jié)構(gòu)和ThreadLocal是一致的,唯一的區(qū)別在于InternalThreadLocalMap 內(nèi)部存儲(chǔ)上,ThreadLocalMap 采用哈希定位實(shí)現(xiàn),而InternalThreadLocalMap 采用數(shù)組常量索引實(shí)現(xiàn),即:
- 每個(gè)FastThreadLocal與一個(gè)固定的數(shù)字常量相關(guān)聯(lián)。
FastThreadLocal內(nèi)部都會(huì)保存一個(gè)index下標(biāo),該下標(biāo)在FastThreadLocal實(shí)例初始化的時(shí)候被賦值:
public class FastThreadLocal<V> {// index 被final修飾,確保FastThreadLocal在InternalThreadLocalMap數(shù)組中的下標(biāo)是固定不變的private final int index;public FastThreadLocal() {// 計(jì)數(shù)器不斷遞增index = InternalThreadLocalMap.nextVariableIndex();}...
}
還有一點(diǎn)也很重要,InternalThreadLocalMap內(nèi)部使用的桶數(shù)組沒有采用弱引用實(shí)現(xiàn),而是普通的強(qiáng)引用:
// 1. InternalThreadLocalMap中桶數(shù)組的實(shí)現(xiàn)private Object[] indexedVariables;// 2. ThreadLocalMap中桶數(shù)組的實(shí)現(xiàn)static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private Entry[] table;
大家可以思考,InternalThreadLocalMap此處不使用弱引用實(shí)現(xiàn),是否存在內(nèi)存泄漏問題 ? 即當(dāng)用戶程序本身失去了對(duì)FastThreadLocal實(shí)例的強(qiáng)引用后,仍然被InternalThreadLocalMap強(qiáng)引用的FastThreadLocal如何被回收掉呢?
這里需要注意一點(diǎn): InternalThreadLocalMap與ThreadLocalMap沒有繼承關(guān)系
set
當(dāng)我們通過FastThreadLocal的set方法設(shè)置值時(shí),其實(shí)和ThreadLocal一樣,還是向InternalThreadLocalMap中設(shè)置值:
public final void set(V value) {// 1. UNSET 是空桶標(biāo)記-->等價(jià)于ThreadLocal中被垃圾回收后key為null的空Entry if (value != InternalThreadLocalMap.UNSET) {// 2. 獲取與當(dāng)前線程關(guān)聯(lián)的InternalThreadLocalMap// 以FastThreadLocal為key,value為val設(shè)置到InternalThreadLocalMap中 InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();setKnownNotUnset(threadLocalMap, value);} else {// 3. 當(dāng)設(shè)置的值為UNSET時(shí),表明需要清空當(dāng)前FastThreadLocalremove();}}
關(guān)于Set的整個(gè)流程,有兩點(diǎn)值得我們思考:
如何獲取當(dāng)前線程私有的InternalThreadLocalMap ?
如果我們當(dāng)前使用的線程類型為FastThreadLocalThread,那么可以直接獲取FastThreadLocalThread內(nèi)部持有的InternalThreadLocalMap:
public class FastThreadLocalThread extends Thread {...// 這一點(diǎn)和Thread內(nèi)部保存ThreadLocalMap實(shí)現(xiàn)一致private InternalThreadLocalMap threadLocalMap;...
}
如果我們當(dāng)前使用的線程類型是原始類型Thread,那么Netty這里會(huì)將InternalThreadLocalMap保存于當(dāng)前線程私有的ThreadLocal內(nèi)部:
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =new ThreadLocal<InternalThreadLocalMap>();...
}
上面兩種獲取方式,前一種被稱為fastGet,而后一種被稱為slowGet :
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public static InternalThreadLocalMap get() {Thread thread = Thread.currentThread();if (thread instanceof FastThreadLocalThread) {return fastGet((FastThreadLocalThread) thread);} else {return slowGet();}}// 1. 當(dāng)前線程類型為FastThreadLocalThread,則直接從獲取其內(nèi)部持有的InternalThreadLocalMap實(shí)例private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();if (threadLocalMap == null) {thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());}return threadLocalMap;}// 2. 當(dāng)前線程類型為傳統(tǒng)的Thread類型,則從當(dāng)前線程私有的ThreadLocal中獲取InternalThreadLocalMap實(shí)例 private static InternalThreadLocalMap slowGet() {InternalThreadLocalMap ret = slowThreadLocalMap.get();if (ret == null) {ret = new InternalThreadLocalMap();slowThreadLocalMap.set(ret);}return ret;} ...
}
如何知道當(dāng)前線程使用到了哪些FastThreadLocal實(shí)例 ?
為什么FastThreadLocal需要獲取到當(dāng)前線程使用到的所有FastThreadLocal實(shí)例呢?
上面說過,InternalThreadLocalMap本身沒有采用弱引用實(shí)現(xiàn),那么Netty就需要另想辦法回收掉失去了用戶程序強(qiáng)引用的FastThreadLocal,防止產(chǎn)生內(nèi)存泄漏。Netty此處采用的方式就是在FastThreadLocalRunnable包裝的Runnable對(duì)象任務(wù)執(zhí)行完畢后,清理掉當(dāng)前線程使用到的所有FastThreadLocal實(shí)現(xiàn)的:
final class FastThreadLocalRunnable implements Runnable {private final Runnable runnable;private FastThreadLocalRunnable(Runnable runnable) {this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");}@Overridepublic void run() {try {runnable.run();} finally {FastThreadLocal.removeAll();}}static Runnable wrap(Runnable runnable) {return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);}
}
那這里還是回歸第二個(gè)問題本身,即如何獲取當(dāng)前線程使用到的所有FastThreadLocal實(shí)例呢?
public class FastThreadLocal<V> {private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {// 1. 嘗試向threadLocalMap中設(shè)置值,如果是第一次設(shè)置,則記錄當(dāng)前線程使用到了當(dāng)前ThreadLocal// (直接常量值定位FastThreadLocal在ThreadLocalMap的哪個(gè)槽中) if (threadLocalMap.setIndexedVariable(index, value)) {// 2. 記錄當(dāng)前線程使用到了當(dāng)前FastThreadLocaladdToVariablesToRemove(threadLocalMap, this);}}private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {// 1. variablesToRemoveIndex固定為0,threadLocalMap數(shù)組第一個(gè)槽位存放當(dāng)前線程使用到的FastThreadLocal集合Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);Set<FastThreadLocal<?>> variablesToRemove;// 2. 說明當(dāng)前FastThreadLocal是當(dāng)前線程第一個(gè)使用到的FastThreadLocal實(shí)例if (v == InternalThreadLocalMap.UNSET || v == null) {// 3. 準(zhǔn)備一個(gè)Set集合variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());// 4. threadLocalMap中的0號(hào)槽位固定存放當(dāng)前線程使用到的FastThreadLocal實(shí)例threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);} else {variablesToRemove = (Set<FastThreadLocal<?>>) v;}// 5. 記錄當(dāng)前FastThreadLocal到集合中去variablesToRemove.add(variable);}...
}public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public boolean setIndexedVariable(int index, Object value) {Object[] lookup = indexedVariables;// 1. 判斷InternalThreadLocalMap是否裝滿if (index < lookup.length) {Object oldValue = lookup[index];lookup[index] = value;// 2. 如果當(dāng)前槽位先前為空,說明是第一次使用到當(dāng)前FastThreadLocalreturn oldValue == UNSET;} else {// 3. 執(zhí)行擴(kuò)容,擴(kuò)容完畢后,在設(shè)置進(jìn)去 --> 說明當(dāng)前FastThreadLocal是第一次被使用expandIndexedVariableTableAndSet(index, value);return true;}}....
}
當(dāng)前線程會(huì)在第一次使用到某個(gè)FastThreadLocal時(shí)進(jìn)行記錄,使用到的FastThreadLocal集合保存在InternalThreadLocalMap數(shù)組的0號(hào)槽位中:
public class FastThreadLocal<V> {// 當(dāng)FastThreadLocal類本身執(zhí)行初始化時(shí),該下標(biāo)就被初始化了,值默認(rèn)為0private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();...
}public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {// 這里的計(jì)數(shù)器也是全局共享的private static final AtomicInteger nextIndex = new AtomicInteger();...public static int nextVariableIndex() { // 每次獲取下標(biāo)時(shí),計(jì)數(shù)器累加一位int index = nextIndex.getAndIncrement();...return index;}
}
set的整個(gè)流程中,我們也可以看出FastThreadLocal快就快在,可以根據(jù)當(dāng)前FastThreadLocal實(shí)例關(guān)聯(lián)的常量值直接定位其在InternalThreadLocalMap中的位置。
get
FastThreadLocal get的流程很簡(jiǎn)單,如下所示:
public class FastThreadLocal<V> {public final V get(InternalThreadLocalMap threadLocalMap) {// 1. 直接常量定位所在槽位Object v = threadLocalMap.indexedVariable(index);// 2. 如果當(dāng)前FastThreadLocal并非首次訪問,則直接對(duì)應(yīng)的值if (v != InternalThreadLocalMap.UNSET) {return (V) v;}// 3. 初始化FastThreadLocalreturn initialize(threadLocalMap);}private V initialize(InternalThreadLocalMap threadLocalMap) {V v = null;try {// 1. 調(diào)用回調(diào)進(jìn)行初始化v = initialValue();} catch (Exception e) {PlatformDependent.throwException(e);}// 2. 設(shè)置初始化的值threadLocalMap.setIndexedVariable(index, v);// 3. 注冊(cè)當(dāng)前FastThreadLocal,即記錄當(dāng)前線程使用了當(dāng)前FastThreadLocal實(shí)例addToVariablesToRemove(threadLocalMap, this);return v;}...
}
垃圾回收
上面說過,InternalThreadLocalMap本身沒有采用弱引用實(shí)現(xiàn),那么Netty就需要另想辦法回收掉失去了用戶程序強(qiáng)引用的FastThreadLocal,防止產(chǎn)生內(nèi)存泄漏。Netty此處采用的方式就是在FastThreadLocalRunnable包裝的Runnable對(duì)象任務(wù)執(zhí)行完畢后,清理掉當(dāng)前線程使用到的所有FastThreadLocal實(shí)現(xiàn)的,這一點(diǎn)上面已經(jīng)提到過了,下面我們看看具體實(shí)現(xiàn)。
final class FastThreadLocalRunnable implements Runnable {private final Runnable runnable;private FastThreadLocalRunnable(Runnable runnable) {this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");}@Overridepublic void run() {try {runnable.run();} finally {FastThreadLocal.removeAll();}}static Runnable wrap(Runnable runnable) {return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);}
}
FastThreadLocal提供了一個(gè)靜態(tài)的removeAll方法,用于清除當(dāng)前線程使用到的所有FastThreadLocal實(shí)例:
public class FastThreadLocal<V> {... public static void removeAll() {// 1. 如果當(dāng)前線程沒有使用到FastThreadLocal,這里直接返回InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();if (threadLocalMap == null) {return;}try {// 2. 獲取固定的0號(hào)槽位保存的Set集合,該集合內(nèi)保存了當(dāng)前線程使用到的所有FastThreadLocal實(shí)例集合 Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);if (v != null && v != InternalThreadLocalMap.UNSET) {Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;// 3. 遍歷該集合內(nèi)每個(gè)FastThreadLocal實(shí)例,依次調(diào)用remove方法 FastThreadLocal<?>[] variablesToRemoveArray =variablesToRemove.toArray(new FastThreadLocal[0]);for (FastThreadLocal<?> tlv: variablesToRemoveArray) {tlv.remove(threadLocalMap);}}} finally {// 4. 置空threadlocalmapInternalThreadLocalMap.remove();}}
- 清空單個(gè)FastThreadLocal
public class FastThreadLocal<V> {public final void remove(InternalThreadLocalMap threadLocalMap) {if (threadLocalMap == null) {return;}// 1. 清除當(dāng)前FastThreadLocal占用的槽位Object v = threadLocalMap.removeIndexedVariable(index);// 2. 取消當(dāng)前FastThreadLocal的注冊(cè)removeFromVariablesToRemove(threadLocalMap, this);// 3. 執(zhí)行回調(diào)通知 if (v != InternalThreadLocalMap.UNSET) {try {onRemoval((V) v);} catch (Exception e) {PlatformDependent.throwException(e);}}}private static void removeFromVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {// 1. 獲取threadlocalmap的0號(hào)槽位保存的set集合 Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);...// 2. 從set集合中移除當(dāng)前fastThreadLocalSet<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;variablesToRemove.remove(variable);}...
}public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public Object removeIndexedVariable(int index) {Object[] lookup = indexedVariables;if (index < lookup.length) {Object v = lookup[index];// 將對(duì)應(yīng)槽位設(shè)置為UNSETlookup[index] = UNSET;return v;} else {return UNSET;}}...
}
- 置空ThreadLocalMap
public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {public static void remove() {Thread thread = Thread.currentThread();// 1. 如果threadLocalMap保存在FastThreadLocalThread內(nèi)部,則直接設(shè)置為nullif (thread instanceof FastThreadLocalThread) {((FastThreadLocalThread) thread).setThreadLocalMap(null);} else {// 2. 如果保存在當(dāng)前線程threadlocal中,則調(diào)用threadlocal的remove方法移除 slowThreadLocalMap.remove();}}...
}
小結(jié)
FastThreadLocal為什么那么快,這個(gè)問題比較好回答:
- 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。
下面給出一個(gè)測(cè)試用例,來看看FastThreadLocal和ThreadLocal在性能上的差異:
public class FastThreadLocalTest {public static void main(String[] args) {new Thread(FastThreadLocalTest::threadLocal).start();new Thread(FastThreadLocalTest::fastThreadLocal).start();}private static void fastThreadLocal() {final int MAX = 100000;long start = System.currentTimeMillis();// DefaultThreadFactory是Netty提供的實(shí)現(xiàn),用于創(chuàng)建類型為FastThreadLocalThread的線程DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class);FastThreadLocal<String>[] fastThreadLocal = new FastThreadLocal[MAX];for (int i = 0; i < MAX; i++) {fastThreadLocal[i] = new FastThreadLocal<>();}// 測(cè)試單線程讀寫FastThreadLocal的耗時(shí)Thread thread = defaultThreadFactory.newThread(() -> {for (int i = 0; i < MAX; i++) {fastThreadLocal[i].set("java: " + i);}System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start));for (int i = 0; i < MAX; i++) {for (int j = 0; j < MAX; 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() {final int MAX = 100000;long start = System.currentTimeMillis();ThreadLocal<String>[] threadLocals = new ThreadLocal[MAX];for (int i = 0; i < MAX; i++) {threadLocals[i] = new ThreadLocal<>();}Thread thread = new Thread(() -> {for (int i = 0; i < MAX; i++) {threadLocals[i].set("java: " + i);}System.out.println("threadLocal set: " + (System.currentTimeMillis() - start));for (int i = 0; i < MAX; i++) {for (int j = 0; j < MAX; j++) {threadLocals[i].get();}}});thread.start();try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("threadLocal total: " + (System.currentTimeMillis() - start));}}
在大量讀寫面前,寫操作的效率差不多,但讀操作 FastThreadLocal 比 ThreadLocal 快的不是一個(gè)數(shù)量級(jí),簡(jiǎn)直是秒殺 ThreadLocal 的存在。
當(dāng)我們把max的值縮小為1000時(shí),此時(shí)讀寫操作不多時(shí),ThreadLocal 明顯更勝一籌!
Netty 中的 FastThreadLocal 在大量頻繁讀寫操作時(shí)效率要高于 ThreadLocal,但要注意結(jié)合 Netty 自帶的線程類使用。
如果沒有大量頻繁讀寫操作的場(chǎng)景,JDK 自帶的 ThreadLocal 足矣,并且性能還要優(yōu)于 FastThreadLocal。