netty系列之:給ThreadLocal插上夢想的翅膀,詳解FastThreadLocal
簡介
JDK中的ThreadLocal可以通過get方法來獲得跟當前執行緒繫結的值。而這些值是儲存在ThreadLocal.ThreadLocalMap中的。而在ThreadLocalMap中底層的資料儲存是一個Entry陣列中的。
那麼從ThreadLocalMap中獲取資料的速度如何呢?速度有沒有可以優化的空間呢?
一起來看看。
從ThreadLocalMap中獲取資料
ThreadLocalMap作為一個Map,它的底層資料儲存是一個Entry型別的陣列:
private Entry[] table;
我們再來回顧一下ThreadLocal是怎麼獲取資料的:
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
首先根據ThreadLocal物件中的threadLocalHashCode跟table的長度進行取模運算,得到要獲取的Entry在table中的位置,然後判斷位置Entry的key是否和要獲取的ThreadLocal物件一致。
如果一致,說明獲取到了ThreadLocal繫結的物件,直接返回即可。
如果不一致,則需要再次進行查詢。
我們看下再次查詢的邏輯:
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
getEntryAfterMiss的邏輯是,先判斷Entry中的物件是否要獲取的物件,如果是則直接返回。
如果Entry中的物件為空,則觸發清除過期Entry的方法。否則的話計算出下一個要判斷的地址,再次進行判斷,直到最終找到要找到的物件為止。
可以看到,如果第一次沒有找到要找到的物件的話,後面則可能會遍歷多次,從而造成執行效率變低。
那麼有沒有可以提升這個尋找速度的方法呢?答案是肯定的。
FastThreadLocal
之前我們提到了,Netty中的本地物件池技術,netty為其建立了一個專門的類叫做Recycler。雖然Recycler中也使用到了ThreadLocal,但是Recycler使用的threadLocal並不是JDK自帶的ThreadLocal,而是FastThreadLocal。和它關聯的ThreadLocalMap叫做InternalThreadLocalMap,和它關聯的Thread叫做FastThreadLocalThread。netty中的類和JDK中的類的對應關係如下:
netty中的物件 | JDK中的物件 |
---|---|
FastThreadLocalThread | Thread |
InternalThreadLocalMap | ThreadLocal.ThreadLocalMap |
FastThreadLocal | ThreadLocal |
我們先來看FastThreadLocalThread。不管它到底快不快,既然是Thread,那麼自然就要繼承自JDK的Thread:
public class FastThreadLocalThread extends Thread
和Thread一樣,FastThreadLocalThread中也有一個ThreadLocalMap,叫做InternalThreadLocalMap,它是FastThreadLocalThread的private屬性:
private InternalThreadLocalMap threadLocalMap;
InternalThreadLocalMap中也有一個ThreadLocal物件,叫做slowThreadLocalMap,是在fastThreadLocalMap不生效的時候使用的。
接下來我們來看下這個ThreadLocalMap為什麼快:
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}
從get方法可以看到,如果當前thread是FastThreadLocalThread的話,則會去呼叫fastGet方法,否則呼叫slowGet方法。
slowGet方法就是使用傳統的ThreadLocal來get:
private static InternalThreadLocalMap slowGet() {
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
我們重點關注下fastGet方法:
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
這裡fast的效果就出現了,fastGet直接返回了thread中的InternalThreadLocalMap物件,不需要進行任何查詢的過程。
再看下FastThreadLocal如何使用get方法來獲取具體的值:
public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
return initialize(threadLocalMap);
}
可以看到FastThreadLocal中的get首先呼叫了InternalThreadLocalMap的get方法,直接返回了FastThreadLocalThread中的InternalThreadLocalMap物件,這個速度是非常快的。
然後直接使用FastThreadLocal中的index,來獲取threadLocalMap中具體儲存資料的陣列中的元素:
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}
因為是直接index訪問的,所以也非常快。這就是fast的由來。
那麼有同學會問題了,FastThreadLocal中的index是怎麼來的呢?
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
而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;
}
也就是說,只要new一個FastThreadLocal,該物件中,就會生成一個唯一的index。然後FastThreadLocal使用該index去InternalThreadLocalMap中存取物件。這樣就不存在ThreadLocal那種需要多次遍歷查詢的情況。
總結
FastThreadLocal是和FastThreadLocalThread配套使用才會真正的fast,否則的話就會fallback到ThreadLocal去執行,大家一定要注意這一點。
更多內容請參考 http://www.flydean.com/48-netty-fastthreadlocal/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!