這篇文章主要講解了“Netty的FastThreadLocal速度快的原因是什么”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Netty的FastThreadLocal速度快的原因是什么”吧!
創(chuàng)新互聯(lián)成立于2013年,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都做網(wǎng)站、成都網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元渾源做網(wǎng)站,已為上家服務(wù),為渾源各地企業(yè)和個人服務(wù),聯(lián)系電話:13518219792
最近在看netty源碼的時候發(fā)現(xiàn)了一個叫FastThreadLocal的類,jdk本身自帶了ThreadLocal類,所以可以大致想到此類比jdk自帶的類速度更快,主要快在什么地方,以及為什么速度更快,下面做一個簡單的分析;
ThreadLocal主要被用在多線程環(huán)境下,方便的獲取當(dāng)前線程的數(shù)據(jù),使用者無需關(guān)心多線程問題,方便使用;為了能說明問題,分別對兩個場景進(jìn)行測試,分別是:多個線程操作同一個ThreadLocal,單線程下的多個ThreadLocal,下面分別測試:
分別對ThreadLocal和FastThreadLocal使用測試代碼,部分代碼如下:
public static void test2() throws Exception { CountDownLatch cdl = new CountDownLatch(10000); ThreadLocalthreadLocal = new ThreadLocal (); long starTime = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { new Thread(new Runnable() { @Override public void run() { threadLocal.set(Thread.currentThread().getName()); for (int k = 0; k < 100000; k++) { threadLocal.get(); } cdl.countDown(); } }, "Thread" + (i + 1)).start(); } cdl.await(); System.out.println(System.currentTimeMillis() - starTime + "ms"); }
以上代碼創(chuàng)建了10000個線程,同時往ThreadLocal設(shè)置,然后get十萬次,然后通過CountDownLatch來計算總的時間消耗,運(yùn)行結(jié)果為:1000ms左右;
下面再對FastThreadLocal進(jìn)行測試,代碼類似:
public static void test2() throws Exception { CountDownLatch cdl = new CountDownLatch(10000); FastThreadLocalthreadLocal = new FastThreadLocal (); long starTime = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { new FastThreadLocalThread(new Runnable() { @Override public void run() { threadLocal.set(Thread.currentThread().getName()); for (int k = 0; k < 100000; k++) { threadLocal.get(); } cdl.countDown(); } }, "Thread" + (i + 1)).start(); } cdl.await(); System.out.println(System.currentTimeMillis() - starTime); }
運(yùn)行之后結(jié)果為:1000ms左右;可以發(fā)現(xiàn)在這種情況下兩種類型的ThreadLocal在性能上并沒有什么差距,下面對第二種情況進(jìn)行測試;
分別對ThreadLocal和FastThreadLocal使用測試代碼,部分代碼如下:
public static void test1() throws InterruptedException { int size = 10000; ThreadLocaltls[] = new ThreadLocal[size]; for (int i = 0; i < size; i++) { tls[i] = new ThreadLocal (); } new Thread(new Runnable() { @Override public void run() { long starTime = System.currentTimeMillis(); for (int i = 0; i < size; i++) { tls[i].set("value" + i); } for (int i = 0; i < size; i++) { for (int k = 0; k < 100000; k++) { tls[i].get(); } } System.out.println(System.currentTimeMillis() - starTime + "ms"); } }).start(); }
以上代碼創(chuàng)建了10000個ThreadLocal,然后使用同一個線程對ThreadLocal設(shè)值,同時get十萬次,運(yùn)行結(jié)果:2000ms左右;
下面再對FastThreadLocal進(jìn)行測試,代碼類似:
public static void test1() { int size = 10000; FastThreadLocaltls[] = new FastThreadLocal[size]; for (int i = 0; i < size; i++) { tls[i] = new FastThreadLocal (); } new FastThreadLocalThread(new Runnable() { @Override public void run() { long starTime = System.currentTimeMillis(); for (int i = 0; i < size; i++) { tls[i].set("value" + i); } for (int i = 0; i < size; i++) { for (int k = 0; k < 100000; k++) { tls[i].get(); } } System.out.println(System.currentTimeMillis() - starTime + "ms"); } }).start(); }
運(yùn)行結(jié)果:30ms左右;可以發(fā)現(xiàn)性能達(dá)到兩個數(shù)量級的差距,當(dāng)然這是在大量訪問次數(shù)的情況下才有的效果;下面重點(diǎn)分析一下ThreadLocal的機(jī)制,以及FastThreadLocal為什么比ThreadLocal更快;
因?yàn)槲覀兂S玫木褪莝et和get方法,分別看一下對應(yīng)的源碼:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
以上代碼大致意思:首先獲取當(dāng)前線程,然后獲取當(dāng)前線程中存儲的threadLocals變量,此變量其實(shí)就是ThreadLocalMap,最后看此ThreadLocalMap是否為空,為空就創(chuàng)建一個新的Map,不為空則以當(dāng)前的ThreadLocal為key,存儲當(dāng)前value;可以進(jìn)一步看一下ThreadLocalMap中的set方法:
private void set(ThreadLocal> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
大致意思:ThreadLocalMap內(nèi)部使用一個數(shù)組來保存數(shù)據(jù),類似HashMap;每個ThreadLocal在初始化的時候會分配一個threadLocalHashCode,然后和數(shù)組的長度進(jìn)行取模操作,所以就會出現(xiàn)hash沖突的情況,在HashMap中處理沖突是使用數(shù)組+鏈表的方式,而在ThreadLocalMap中,可以看到直接使用nextIndex,進(jìn)行遍歷操作,明顯性能更差;下面再看一下get方法:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
同樣是先獲取當(dāng)前線程,然后獲取當(dāng)前線程中的ThreadLocalMap,然后以當(dāng)前的ThreadLocal為key,到ThreadLocalMap中獲取value:
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); } 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; }
同set方式,通過取模獲取數(shù)組下標(biāo),如果沒有沖突直接返回數(shù)據(jù),否則同樣出現(xiàn)遍歷的情況;所以通過分析可以大致知道以下幾個問題:
1.ThreadLocalMap是存放在Thread下面的,ThreadLocal作為key,所以多個線程操作同一個ThreadLocal其實(shí)就是在每個線程的ThreadLocalMap中插入的一條記錄,不存在任何沖突問題;
2.ThreadLocalMap在解決沖突時,通過遍歷的方式,非常影響性能;
3.FastThreadLocal通過其他方式解決沖突的問題,達(dá)到性能的優(yōu)化;
下面繼續(xù)來看一下FastThreadLocal是通過何種方式達(dá)到性能的優(yōu)化。
Netty中分別提供了FastThreadLocal和FastThreadLocalThread兩個類,F(xiàn)astThreadLocalThread繼承于Thread,下面同樣對常用的set和get方法來進(jìn)行源碼分析:
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); } }
此處首先對value進(jìn)行判定是否為InternalThreadLocalMap.UNSET,然后同樣使用了一個InternalThreadLocalMap用來存放數(shù)據(jù):
public static InternalThreadLocalMap get() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return fastGet((FastThreadLocalThread) thread); } else { return slowGet(); } } private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); if (threadLocalMap == null) { thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap()); } return threadLocalMap; }
可以發(fā)現(xiàn)InternalThreadLocalMap同樣存放在FastThreadLocalThread中,不同在于,不是使用ThreadLocal對應(yīng)的hash值取模獲取位置,而是直接使用FastThreadLocal的index屬性,index在實(shí)例化時被初始化:
private final int index; public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); }
再進(jìn)入nextVariableIndex方法中:
static final AtomicInteger nextIndex = new AtomicInteger(); public static int nextVariableIndex() { int index = nextIndex.getAndIncrement(); if (index < 0) { nextIndex.decrementAndGet(); throw new IllegalStateException("too many thread-local indexed variables"); } return index; }
在InternalThreadLocalMap中存在一個靜態(tài)的nextIndex對象,用來生成數(shù)組下標(biāo),因?yàn)槭庆o態(tài)的,所以每個FastThreadLocal生成的index是連續(xù)的,再看一下InternalThreadLocalMap中是如何setIndexedVariable的:
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; } }
indexedVariables是一個對象數(shù)組,用來存放value;直接使用index作為數(shù)組下標(biāo)進(jìn)行存放;如果index大于數(shù)組長度,進(jìn)行擴(kuò)容;get方法直接通過FastThreadLocal中的index進(jìn)行快速讀?。?/p>
public final V get(InternalThreadLocalMap threadLocalMap) { Object v = threadLocalMap.indexedVariable(index); if (v != InternalThreadLocalMap.UNSET) { return (V) v; } return initialize(threadLocalMap); } public Object indexedVariable(int index) { Object[] lookup = indexedVariables; return index < lookup.length? lookup[index] : UNSET; }
直接通過下標(biāo)進(jìn)行讀取,速度非???;但是這樣會有一個問題,可能會造成空間的浪費(fèi);
通過以上分析我們可以知道在有大量的ThreadLocal進(jìn)行讀寫操作的時候,才可能會遇到性能問題;另外FastThreadLocal通過空間換取時間的方式來達(dá)到O(1)讀取數(shù)據(jù);還有一個疑問就是內(nèi)部為什么不直接使用HashMap(數(shù)組+黑紅樹)來代替ThreadLocalMap。
感謝各位的閱讀,以上就是“Netty的FastThreadLocal速度快的原因是什么”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Netty的FastThreadLocal速度快的原因是什么這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!