1. 程式人生 > >Netty學習之旅----ThreadLocal原理分析與效能優化思考(思考篇)

Netty學習之旅----ThreadLocal原理分析與效能優化思考(思考篇)

/**
     * 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與InternalThreadLocalMap

InternalThreadLocalMap是執行緒儲存本地變數的資料結構,每個執行緒擁有自己的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方法入手即可,知道上述原理後,原始碼的閱讀應該比較容易,就不做過多講解了。

--------------------- 本文來自 唯有堅持不懈 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/prestigeding/article/details/54945658?utm_source=copy