/** * 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,獲取當前線程。代碼@2,從當前線程獲取ThreadLocalMap,ThreadLocalMap getMap(Thread t) { return t.threadLocals; },這里是直接返回線程對象的threadLocals變量,有點意思吧,所以說ThreadLocal,是線程的本地變量,就是這層意思,真正存放數據的地方,就是線程對象本身,其實接下來的會更加有意思:我們進入ThreadLocalMap源碼分析,得知,原來ThreadLocalMap就是一個Map結構(K-V)鍵值對,關于里面的源碼就不一一分析了,ThreadLocalMap(ThreadLocal firstKey, Object firstValue),firstKey 為ThreadLocal,神奇吧,其實這也是為什么Thread的本地變量的數據類型為Map的原型,一個線程可以被多個ThreadLocal關聯,每聲明一個,就在線程的threadLocals增加為一個鍵值對,key 為 ThreadLocal,而value為具體存放的對象。代碼@3,如果線程的ThreadLocalMap不為空,則直接返回對,否則進入到代碼@4代碼@4,初始化并獲取放入ThreadLocal中的變量。上面就是ThreadLocal的核心設計理念,為了更加直觀的說明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(); }}//一個線程調用 ThreadLocalDemo1 的 test1方法,在這個執行鏈中會涉及到兩個ThreadLocal變量,調用ThreadLocal的get方法,首先會獲取當前線程,然后從當前線程對象中獲取線程內部屬性[ThreadLocal.ThreadLocalMap threadLocals = null;],然后從ThreadLocalMap中以ThreadLocal對象為鍵,從threadLocals map中獲取存放的值。線程的threadLocals值為{ ThreadLocalDemo1.schemaLocal : 該變量中的值, ThreadLocalDemo2.scloal : 存放在本線程中的值 }------------------------------------------------------------------------------------3、 ThreadLocal優化思考 ThreadLocal的數據訪問算法,本質上就是Map的訪問特性。 我在分析HashMap源碼的時候,已經將HashMap的存儲結構講解完畢,如有興趣,可以瀏覽一下我的博文:深入理解HashMap:http://blog.csdn.net/prestigeding/article/details/52861420,HashMap根據key的訪問速度效率是很快的,為什么呢?因為HashMap根據key的hash,然后會定位到內部的數據槽(該數據是數組結構),眾所周知,根據數組的下標訪問,訪問速度是最快的,也就是說HashMap根據key的定位速度比LinkedList等都快,僅次于數組訪問方式,這是因為HashMap多了一步Hash定位槽的過程(當然,如果有Hash沖突那就更慢了)。所以,如果在高并發場景下,需要進一步優化ThreadLocal的訪問性能,那就要從線程對象(Thread的threadLocals 數據結構下手了,如果能將數據結構修改為數組,然后每個ThreadLocal對象維護其下標那就完美了)。是的,Netty框架就是為了高并發而生的,由于并發訪問的數量很大,一點點的性能優化,就會帶來可觀的性能提升效應,Netty主要從如下兩個方面對ThreadLocal的實現進行優化1)線程對象直接提供 set、get方法,以便直接獲取線程本地存儲相關的變量屬性。2)將數據存儲基于數組存儲。4、Netty關于ThreadLocal機制的優化由于ThreadLocal是JDK的原生實現,通用性很強,直接擴展進行定制化不是明智的選擇,故Netty在優化ThreadLocal的方式是自己另起灶爐,實現ThreadLocal的語義。優化方法如下:1)提供一個接口,FastThreadLocalaccess,并對線程池工廠類進行定制,創建的線程繼承在java.lang.Thread類,并實現FastThreadLocalAccess接口,提供直接設置,獲取線程本地變量的方法。2)提供FastThreadLocal類,此類實現ThreadLocal相同的語義。3)提供InternalThreadLocalMap類,此類作用類同于java.lang.ThreadLocal.ThreadLocalMap類,用于線程存放真實數據的結構。4.1 擴展線程對象,提供set,get方法 通過定制的線程池工廠,創建的線程對象為擴展后的線程對象,在Netty中對應為FastThreadLocalThread,該類本身很簡單,值得大家注意的是其思想,jdk并發包中提供的線程池實現機制中,提供了線程創建的工廠的擴展點,這里就是其典型的實踐。 這里附上其源碼,不做解讀: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是線程存儲本地變量的數據結構,每個線程擁有自己的InternalThreadLocalMap,其作用與java.lang.ThreadLocal.ThreadLocalMap內部類一樣,而FastThreadLocal,其語義與ThreadLocal一樣,對外表現與ThreadLocal一樣。再次重復一下,Netty的InternalThreadLocalMap內部為數組,為什么是數組呢?線程本地變量,要從線程的執行流的角度看,一個線程在執行過程中,會經過多個類,會有多個類中聲明有線程本地變量(參考上文說明ThreadLocal時候的舉例),所以此處的數組就是保留線程在整個線程的執行過程中,不同的ThreadLocal變量中保存不同的數據,java.lang.ThreadLocal.ThreadLocalMap內部類的實現使用map結構,鍵為 ThreadLocal對象,而值為真正保存的變量值,InternalThreadLocalMap既然是數組,數組是一維的,數組最終肯定只能保存 真正要保持的變量值,那怎么區分不同的ThreadLocal在InternalThreadLocalMap中的下標呢?Netty采用的方式是將下標保存在FastThreadLocal中,我們知道,一般使用本地線程變量,FastThreadLocal的聲明方式,一般是類變量(靜態變量),諸如:private static final ThreadLocal aThreadLocal = new ThreadLocal();整個系統ThreadLocal的個數其實不會很多,每個FastThreadLocal在InternalThreadLocalMap的下標(偏移量)在FastThreadLocal加載時候確定,并保持不變。并且每個InternalThreadLocal內部數組的第一元素,存放系統運行中的FastThreadLocal對象。InternalThreadLocalMap的內部數據結構為:/** Used by {@link FastThreadLocal} */Object[] indexedVariables;現在舉例說明上述理論,比如整個項目,有A,B,C,D,E5個類中各聲明了一個靜態的FastThreadLocal變量,類的加載順序為 A , B , D, E, C那們 類A中的FastThreadLocal存放在線程變量InternalThreadLocalMap的下標為1,B,為2,D為3,依次內推,比如線程 T1,在一次請求過程中,需要用的A,E,C三個類中的FastThreadLocalMap,那么線程T1,的InternalThreadLocalMap的水庫中的下標為1為A,下標為2,3的元素為空,下標為4為E,下標5存放C中的變量值。每個線程InternalThreadLocalMap下標為0的是一Set集合,存放的是系統運行中有效的FastThreadLocal變量。根據這樣的存放后,FastThreadLocal 的get,set方法,都是根據下標直接在InternalThreadLocalMap的數組中直接存儲,當然,值得一提的是InternalThreadLocalMap中數組元素長度默認為32,如果系統的FastThreadLocal的數量超過32個的話,會成倍擴容。FastThreadLocal學習的入口,建議從set,get方法入手即可,知道上述原理后,源碼的閱讀應該比較容易,就不做過多講解了。 如果愿意分享技術心得,歡迎加群:互聯網技術交流群 34265357
新聞熱點
疑難解答