簡(jiǎn)介
JDK中的ThreadLocal可以通過(guò)get方法來(lái)獲得跟當(dāng)前線程綁定的值。而這些值是存儲(chǔ)在ThreadLocal.ThreadLocalMap中的。而在ThreadLocalMap中底層的數(shù)據(jù)存儲(chǔ)是一個(gè)Entry數(shù)組中的。
那么從ThreadLocalMap中獲取數(shù)據(jù)的速度如何呢?速度有沒(méi)有可以優(yōu)化的空間呢?
一起來(lái)看看。
從ThreadLocalMap中獲取數(shù)據(jù)
ThreadLocalMap作為一個(gè)Map,它的底層數(shù)據(jù)存儲(chǔ)是一個(gè)Entry類型的數(shù)組:
private Entry[] table;
我們?cè)賮?lái)回顧一下ThreadLocal是怎么獲取數(shù)據(jù)的:
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);
}
首先根據(jù)ThreadLocal對(duì)象中的threadLocalHashCode跟table的長(zhǎng)度進(jìn)行取模運(yùn)算,得到要獲取的Entry在table中的位置,然后判斷位置Entry的key是否和要獲取的ThreadLocal對(duì)象一致。
如果一致,說(shuō)明獲取到了ThreadLocal綁定的對(duì)象,直接返回即可。
如果不一致,則需要再次進(jìn)行查找。
我們看下再次查找的邏輯:
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中的對(duì)象是否要獲取的對(duì)象,如果是則直接返回。
如果Entry中的對(duì)象為空,則觸發(fā)清除過(guò)期Entry的方法。否則的話計(jì)算出下一個(gè)要判斷的地址,再次進(jìn)行判斷,直到最終找到要找到的對(duì)象為止。
可以看到,如果第一次沒(méi)有找到要找到的對(duì)象的話,后面則可能會(huì)遍歷多次,從而造成執(zhí)行效率變低。
那么有沒(méi)有可以提升這個(gè)尋找速度的方法呢?答案是肯定的。
FastThreadLocal
之前我們提到了,Netty中的本地對(duì)象池技術(shù),netty為其創(chuàng)建了一個(gè)專門(mén)的類叫做Recycler。雖然Recycler中也使用到了ThreadLocal,但是Recycler使用的threadLocal并不是JDK自帶的ThreadLocal,而是FastThreadLocal。和它關(guān)聯(lián)的ThreadLocalMap叫做InternalThreadLocalMap,和它關(guān)聯(lián)的Thread叫做FastThreadLocalThread。netty中的類和JDK中的類的對(duì)應(yīng)關(guān)系如下:
| netty中的對(duì)象 | JDK中的對(duì)象 |
|---|---|
| FastThreadLocalThread | Thread |
| InternalThreadLocalMap | ThreadLocal.ThreadLocalMap |
| FastThreadLocal | ThreadLocal |
我們先來(lái)看FastThreadLocalThread。不管它到底快不快,既然是Thread,那么自然就要繼承自JDK的Thread:
public class FastThreadLocalThread extends Thread
和Thread一樣,F(xiàn)astThreadLocalThread中也有一個(gè)ThreadLocalMap,叫做InternalThreadLocalMap,它是FastThreadLocalThread的private屬性:
private InternalThreadLocalMap threadLocalMap;
InternalThreadLocalMap中也有一個(gè)ThreadLocal對(duì)象,叫做slowThreadLocalMap,是在fastThreadLocalMap不生效的時(shí)候使用的。
接下來(lái)我們來(lái)看下這個(gè)ThreadLocalMap為什么快:
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
return fastGet((FastThreadLocalThread) thread);
} else {
return slowGet();
}
}
從get方法可以看到,如果當(dāng)前thread是FastThreadLocalThread的話,則會(huì)去調(diào)用fastGet方法,否則調(diào)用slowGet方法。
slowGet方法就是使用傳統(tǒng)的ThreadLocal來(lái)get:
private static InternalThreadLocalMap slowGet() {
InternalThreadLocalMap ret = slowThreadLocalMap.get();
if (ret == null) {
ret = new InternalThreadLocalMap();
slowThreadLocalMap.set(ret);
}
return ret;
}
我們重點(diǎn)關(guān)注下fastGet方法:
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
這里fast的效果就出現(xiàn)了,fastGet直接返回了thread中的InternalThreadLocalMap對(duì)象,不需要進(jìn)行任何查找的過(guò)程。
再看下FastThreadLocal如何使用get方法來(lái)獲取具體的值:
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首先調(diào)用了InternalThreadLocalMap的get方法,直接返回了FastThreadLocalThread中的InternalThreadLocalMap對(duì)象,這個(gè)速度是非??斓?。
然后直接使用FastThreadLocal中的index,來(lái)獲取threadLocalMap中具體存儲(chǔ)數(shù)據(jù)的數(shù)組中的元素:
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}
因?yàn)槭侵苯觟ndex訪問(wèn)的,所以也非??臁_@就是fast的由來(lái)。
那么有同學(xué)會(huì)問(wèn)題了,F(xiàn)astThreadLocal中的index是怎么來(lái)的呢?
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
而InternalThreadLocalMap中的nextVariableIndex方法是一個(gè)靜態(tài)方法:
public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
也就是說(shuō),只要new一個(gè)FastThreadLocal,該對(duì)象中,就會(huì)生成一個(gè)唯一的index。然后FastThreadLocal使用該index去InternalThreadLocalMap中存取對(duì)象。這樣就不存在ThreadLocal那種需要多次遍歷查找的情況。
總結(jié)
FastThreadLocal是和FastThreadLocalThread配套使用才會(huì)真正的fast,否則的話就會(huì)fallback到ThreadLocal去執(zhí)行,大家一定要注意這一點(diǎn)。
更多內(nèi)容請(qǐng)參考 http://www.flydean.com/48-netty-fastthreadlocal/
最通俗的解讀,最深刻的干貨,最簡(jiǎn)潔的教程,眾多你不知道的小技巧等你來(lái)發(fā)現(xiàn)!
歡迎關(guān)注我的公眾號(hào):「程序那些事」,懂技術(shù),更懂你!