/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); //@1 ThreadLocalMap map = getMap(t); //@2 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //@3 if (e != null) return (T)e.value; } return setInitialValue(); // @4 }代碼@1,獲取當(dāng)前線程。代碼@2,從當(dāng)前線程獲取ThreadLocalMap,ThreadLocalMap getMap(Thread t) { return t.threadLocals; },這里是直接返回線程對象的threadLocals變量,有點(diǎn)意思吧,所以說ThreadLocal,是線程的本地變量,就是這層意思,真正存放數(shù)據(jù)的地方,就是線程對象本身,其實接下來的會更加有意思:我們進(jìn)入ThreadLocalMap源碼分析,得知,原來ThreadLocalMap就是一個Map結(jié)構(gòu)(K-V)鍵值對,關(guān)于里面的源碼就不一一分析了,ThreadLocalMap(ThreadLocal firstKey, Object firstValue),firstKey 為ThreadLocal,神奇吧,其實這也是為什么Thread的本地變量的數(shù)據(jù)類型為Map的原型,一個線程可以被多個ThreadLocal關(guān)聯(lián),每聲明一個,就在線程的threadLocals增加為一個鍵值對,key 為 ThreadLocal,而value為具體存放的對象。代碼@3,如果線程的ThreadLocalMap不為空,則直接返回對,否則進(jìn)入到代碼@4代碼@4,初始化并獲取放入ThreadLocal中的變量。上面就是ThreadLocal的核心設(shè)計理念,為了更加直觀的說明ThreadLocal原理,舉例說明:-----------------------------------------------------------------------public class ThreadLocalDemo1 { PRivate static final ThreadLocal<String> schemaLocal = new ThreadLocal<String>(); public void test1() { String a = schemaLocal.get(); ThreadLocalDemo2 demo2 = new ThreadLocalDemo2(); demo2.test(a); } public static void main(String[] args) { // TODO Auto-generated method stub }}public class ThreadLocalDemo2 { private static final ThreadLocal<String> slocal = new ThreadLocal<String>(); public void test(String b) { String a = slocal.get(); // 其他代碼 System.out.println(b); } public static void main(String[] args) { // TODO Auto-generated method stub }}public class TestMain { public static void main(String[] args) { // TODO Auto-generated method stub ThreadLocalDemo1 d = new ThreadLocalDemo1(); d.test1(); }}//一個線程調(diào)用 ThreadLocalDemo1 的 test1方法,在這個執(zhí)行鏈中會涉及到兩個ThreadLocal變量,調(diào)用ThreadLocal的get方法,首先會獲取當(dāng)前線程,然后從當(dāng)前線程對象中獲取線程內(nèi)部屬性[ThreadLocal.ThreadLocalMap threadLocals = null;],然后從ThreadLocalMap中以ThreadLocal對象為鍵,從threadLocals map中獲取存放的值。線程的threadLocals值為{ ThreadLocalDemo1.schemaLocal : 該變量中的值, ThreadLocalDemo2.scloal : 存放在本線程中的值 }------------------------------------------------------------------------------------3、 ThreadLocal優(yōu)化思考 ThreadLocal的數(shù)據(jù)訪問算法,本質(zhì)上就是Map的訪問特性。 我在分析HashMap源碼的時候,已經(jīng)將HashMap的存儲結(jié)構(gòu)講解完畢,如有興趣,可以瀏覽一下我的博文:深入理解HashMap:http://blog.csdn.net/prestigeding/article/details/52861420,HashMap根據(jù)key的訪問速度效率是很快的,為什么呢?因為HashMap根據(jù)key的hash,然后會定位到內(nèi)部的數(shù)據(jù)槽(該數(shù)據(jù)是數(shù)組結(jié)構(gòu)),眾所周知,根據(jù)數(shù)組的下標(biāo)訪問,訪問速度是最快的,也就是說HashMap根據(jù)key的定位速度比LinkedList等都快,僅次于數(shù)組訪問方式,這是因為HashMap多了一步Hash定位槽的過程(當(dāng)然,如果有Hash沖突那就更慢了)。所以,如果在高并發(fā)場景下,需要進(jìn)一步優(yōu)化ThreadLocal的訪問性能,那就要從線程對象(Thread的threadLocals 數(shù)據(jù)結(jié)構(gòu)下手了,如果能將數(shù)據(jù)結(jié)構(gòu)修改為數(shù)組,然后每個ThreadLocal對象維護(hù)其下標(biāo)那就完美了)。是的,Netty框架就是為了高并發(fā)而生的,由于并發(fā)訪問的數(shù)量很大,一點(diǎn)點(diǎn)的性能優(yōu)化,就會帶來可觀的性能提升效應(yīng),Netty主要從如下兩個方面對ThreadLocal的實現(xiàn)進(jìn)行優(yōu)化1)線程對象直接提供 set、get方法,以便直接獲取線程本地存儲相關(guān)的變量屬性。2)將數(shù)據(jù)存儲基于數(shù)組存儲。4、Netty關(guān)于ThreadLocal機(jī)制的優(yōu)化由于ThreadLocal是JDK的原生實現(xiàn),通用性很強(qiáng),直接擴(kuò)展進(jìn)行定制化不是明智的選擇,故Netty在優(yōu)化ThreadLocal的方式是自己另起灶爐,實現(xiàn)ThreadLocal的語義。優(yōu)化方法如下:1)提供一個接口,F(xiàn)astThreadLocalaccess,并對線程池工廠類進(jìn)行定制,創(chuàng)建的線程繼承在java.lang.Thread類,并實現(xiàn)FastThreadLocalAccess接口,提供直接設(shè)置,獲取線程本地變量的方法。2)提供FastThreadLocal類,此類實現(xiàn)ThreadLocal相同的語義。3)提供InternalThreadLocalMap類,此類作用類同于java.lang.ThreadLocal.ThreadLocalMap類,用于線程存放真實數(shù)據(jù)的結(jié)構(gòu)。4.1 擴(kuò)展線程對象,提供set,get方法 通過定制的線程池工廠,創(chuàng)建的線程對象為擴(kuò)展后的線程對象,在Netty中對應(yīng)為FastThreadLocalThread,該類本身很簡單,值得大家注意的是其思想,jdk并發(fā)包中提供的線程池實現(xiàn)機(jī)制中,提供了線程創(chuàng)建的工廠的擴(kuò)展點(diǎn),這里就是其典型的實踐。 這里附上其源碼,不做解讀:public class FastThreadLocalThread extends Thread implements FastThreadLocalAccess { private InternalThreadLocalMap threadLocalMap; public FastThreadLocalThread() { } public FastThreadLocalThread(Runnable target) { super(target); } public FastThreadLocalThread(ThreadGroup group, Runnable target) { super(group, target); } public FastThreadLocalThread(String name) { super(name); } public FastThreadLocalThread(ThreadGroup group, String name) { super(group, name); } public FastThreadLocalThread(Runnable target, String name) { super(target, name); } public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) { super(group, target, name); } public FastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) { super(group, target, name, stackSize); } /** * Returns the internal data structure that keeps the thread-local variables bound to this thread. * Note that this method is for internal use only, and thus is subject to change at any time. */ @Override public final InternalThreadLocalMap threadLocalMap() { return threadLocalMap; } /** * Sets the internal data structure that keeps the thread-local variables bound to this thread. * Note that this method is for internal use only, and thus is subject to change at any time. */ @Override public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) { this.threadLocalMap = threadLocalMap; }}4.2 FastThreadLocal與InternalThreadLocalMapInternalThreadLocalMap是線程存儲本地變量的數(shù)據(jù)結(jié)構(gòu),每個線程擁有自己的InternalThreadLocalMap,其作用與java.lang.ThreadLocal.ThreadLocalMap內(nèi)部類一樣,而FastThreadLocal,其語義與ThreadLocal一樣,對外表現(xiàn)與ThreadLocal一樣。再次重復(fù)一下,Netty的InternalThreadLocalMap內(nèi)部為數(shù)組,為什么是數(shù)組呢?線程本地變量,要從線程的執(zhí)行流的角度看,一個線程在執(zhí)行過程中,會經(jīng)過多個類,會有多個類中聲明有線程本地變量(參考上文說明ThreadLocal時候的舉例),所以此處的數(shù)組就是保留線程在整個線程的執(zhí)行過程中,不同的ThreadLocal變量中保存不同的數(shù)據(jù),java.lang.ThreadLocal.ThreadLocalMap內(nèi)部類的實現(xiàn)使用map結(jié)構(gòu),鍵為 ThreadLocal對象,而值為真正保存的變量值,InternalThreadLocalMap既然是數(shù)組,數(shù)組是一維的,數(shù)組最終肯定只能保存 真正要保持的變量值,那怎么區(qū)分不同的ThreadLocal在InternalThreadLocalMap中的下標(biāo)呢?Netty采用的方式是將下標(biāo)保存在FastThreadLocal中,我們知道,一般使用本地線程變量,F(xiàn)astThreadLocal的聲明方式,一般是類變量(靜態(tài)變量),諸如:private static final ThreadLocal aThreadLocal = new ThreadLocal();整個系統(tǒng)ThreadLocal的個數(shù)其實不會很多,每個FastThreadLocal在InternalThreadLocalMap的下標(biāo)(偏移量)在FastThreadLocal加載時候確定,并保持不變。并且每個InternalThreadLocal內(nèi)部數(shù)組的第一元素,存放系統(tǒng)運(yùn)行中的FastThreadLocal對象。InternalThreadLocalMap的內(nèi)部數(shù)據(jù)結(jié)構(gòu)為:/** Used by {@link FastThreadLocal} */Object[] indexedVariables;現(xiàn)在舉例說明上述理論,比如整個項目,有A,B,C,D,E5個類中各聲明了一個靜態(tài)的FastThreadLocal變量,類的加載順序為 A , B , D, E, C那們 類A中的FastThreadLocal存放在線程變量InternalThreadLocalMap的下標(biāo)為1,B,為2,D為3,依次內(nèi)推,比如線程 T1,在一次請求過程中,需要用的A,E,C三個類中的FastThreadLocalMap,那么線程T1,的InternalThreadLocalMap的水庫中的下標(biāo)為1為A,下標(biāo)為2,3的元素為空,下標(biāo)為4為E,下標(biāo)5存放C中的變量值。每個線程InternalThreadLocalMap下標(biāo)為0的是一Set集合,存放的是系統(tǒng)運(yùn)行中有效的FastThreadLocal變量。根據(jù)這樣的存放后,F(xiàn)astThreadLocal 的get,set方法,都是根據(jù)下標(biāo)直接在InternalThreadLocalMap的數(shù)組中直接存儲,當(dāng)然,值得一提的是InternalThreadLocalMap中數(shù)組元素長度默認(rèn)為32,如果系統(tǒng)的FastThreadLocal的數(shù)量超過32個的話,會成倍擴(kuò)容。FastThreadLocal學(xué)習(xí)的入口,建議從set,get方法入手即可,知道上述原理后,源碼的閱讀應(yīng)該比較容易,就不做過多講解了。 如果愿意分享技術(shù)心得,歡迎加群:互聯(lián)網(wǎng)技術(shù)交流群 34265357
新聞熱點(diǎn)
疑難解答