Java併發編程:ThreadLocal



ThreadLocal特性及使用場景:
1、方便同一個線程使用某一對象,避免不必要的參數傳遞;
2、線程間數據隔離(每個線程在自己線程裏使用自己的局部變量,各線程間的ThreadLocal對象互不影響);
3、獲取數據庫連接、Session、關聯ID(比如日誌的uniqueID,方便串起多個日誌);

ThreadLocal應注意:
1、ThreadLocal並未解決多線程訪問共享對象的問題;
2、ThreadLocal並不是每個線程拷貝一個對象,而是直接new(新建)一個;
3、如果ThreadLocal.set()的對象是多線程共享的,那麼還是涉及併發問題。
1、ThreadLocal<T>初始化
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT= 0x61c88647;
/**
* The next hash code to be given out. Updated atomically. Starts at zero.
*/
// 源碼說nextHashCode初始值爲0,但實際調試時顯示初始值爲1253254570,費解?
// 而且當初始化完畢後,nextHashCode的值又變爲0,說明其初始值確實是0的。
  1. private static AtomicInteger nextHashCode = new AtomicInteger();  
  2. private static intnextHashCode() {  
  3.     return nextHashCode.getAndAdd(HASH_INCREMENT);  
  4. }  
ThreadLocal類變量有3個,其中2個是靜態變量(包括一個常量),實際作爲作爲ThreadLocal實例的變量只有threadLocalHashCode這1個,而且已經初始化就不可變了。
創建ThreadLocal實例時有哪些操作呢:
ThreadLocal初始化時會調用nextHashCode()方法初始化threadLocalHashCode,且threadLocalHashCode 初始化後不可變threadLocalHashCode可用來標記不同的ThreadLocal實例。


2、內部類
2.1 ThreadLocalMap
ThreadLocalMap是 定製的hashMap,僅用於維護當前線程的本地變量值。僅ThreadLocal類對其有操作權限,是Thread的私有屬性。爲避免佔用空間較大或生命週期較長的數據常駐於內存引發一系列問題,hash table的key是弱引用WeakReferences。當空間不足時,會清理未被引用的entry。
ThreadLocalMap中的重點:
  1. static class Entryextends WeakReference<ThreadLocal<?>>{  
  2.    /** The value associated with this ThreadLocal. */  
  3.     Object value;  
  4.      Entry(ThreadLocal<?> k, Object v){  
  5.        super(k);  
  6.         value= v;  
  7.    }  
  8. }  
Note:
ThreadLocalMap的key是ThreadLocal,value是Object(即我們所謂的「線程本地數據」)。

2.2 SuppliedThreadLocal<T> extends ThreadLocal<T>
SuppliedThreadLocal是JDK8新增的內部類,只是擴展了ThreadLocal的初始化值的方法而已,允許使用JDK8新增的Lambda表達式賦值。需要注意的是,函數式接口Supplier不允許爲null。
源碼如下:
  1. static final class SuppliedThreadLocal<T>extends ThreadLocal<T>{  
  2.     private final Supplier<?extends T> supplier;  
  3.      SuppliedThreadLocal(Supplier<?extends T> supplier){  
  4.        this.supplier= Objects.requireNonNull(supplier);  
  5.    }  
  6.      @Override  
  7.    protected T initialValue(){  
  8.        return supplier.get();  
  9.    }  
  10. }  

3、主要方法
3.1、T get()
返回當前線程的value。
  1. public T get(){  
  2.     Thread t= Thread.currentThread(); // 獲取當前線程  
  3.     ThreadLocalMap map= getMap(t); // 獲取當前線程對應的Map  
  4.    if(map!=null){  
  5.         ThreadLocalMap.Entry e = map.getEntry(this); // 詳見3.1.1  
  6.        if(e!=null){ // map不爲空且當前線程有value,返回value  
  7.             @SuppressWarnings("unchecked")  
  8.             T result=(T)e.value;  
  9.            return result;  
  10.        }  
  11.    }  
  12.    return setInitialValue(); // 初始化再返回值  
  13. }  
-----
getMap的源碼:
  1. ThreadLocalMap getMap(Thread t){  
  2.    returnt.threadLocals;  
  3. }  
getMap(t)返回當前線程的成員變量ThreadLocalMap(Thread的成員變量有ThreadLocalMap,這一點可以查看Thread的源碼,如下)很明確的說明了 ThreadLocal屬於線程,ThreadLocalMap由ThreadLocal持有,說到底,ThreadLocalMap 也是線程所持有。每個線程Thread都有自己的ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

--------
setInitialValue源碼:
  1. private T setInitialValue(){  
  2.     T value= initialValue(); //調用重寫的initialValue,返回新值  
  3.     Thread t= Thread.currentThread();  
  4.     ThreadLocalMap map= getMap(t);  
  5.    if(map!=null// 當前線程的ThreadLocalMap不爲空,則直接賦值  
  6.         map.set(this, value);  
  7.    else  
  8. // 爲當前線程創造一個ThreadLocalMap(this, firstValue)並賦初值,this爲當前線程  
  9.         createMap(t, value);  
  10.    return value;  
  11. }  
  12. protected T initialValue() {  
  13.     return T; // 自定義返回值  
  14. };  
createMap源碼:
  1. void createMap(Thread t, T firstValue){  
  2.     t.threadLocals=new ThreadLocalMap(this, firstValue);  
  3. }  
ThreadLocal之get流程:
1、獲取當前線程t;
2、返回當前線程t的成員變量ThreadLocalMap(以下簡寫map);
3、map不爲null,則獲取以當前線程爲key的ThreadLocalMap的Entry(以下簡寫e),如果e不爲null,則直接返回該Entry的value;
4、如果map爲null或者e爲null,返回setInitialValue()的值。setInitialValue()調用重寫的 initialValue()返回新值(如果沒有重寫initialValue將返回默認值null ),並將新值存入當前線程的ThreadLocalMap(如果當前線程沒有ThreadLocalMap,會先創建一個)。


3.2、void set(T value)
爲【當前線程】的【當前ThreadLocal】賦值(初始值or新值)。和setInitialValue相當相似,就不多分析了。
  1. public void set(T value){  
  2.     Thread t= Thread.currentThread();  
  3.     ThreadLocalMap map= getMap(t);  
  4.    if(map!=null)  
  5.         map.set(this, value);  
  6.    else  
  7.         createMap(t, value);  
  8. }  

3.3、void remove()
獲取當前線程的ThreadLocalMap,map不爲空,則移除當前ThreadLocal作爲key的鍵值對。
  1. public void remove(){  
  2.      ThreadLocalMap m= getMap(Thread.currentThread());  
  3.     if(m!=null)  
  4.          m.remove(this);  
  5.  }  
Note:
remove()移除當前線程的當前ThreadLocal數據(只是清空該key-value鍵值對),而且是 立即移除,移除後,再調用get方法將重新調用initialValue方法初始化(除非在此期間調用了set方法賦值)。


3.4、static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
JDK8新增,支持Lambda表達式,和 ThreadLocal重寫的initialValue()效果一樣。
  1. public static<S> ThreadLocal<S> withInitial(Supplier<?extends S> supplier){  
  2.    return new SuppliedThreadLocal<>(supplier);  
  3. }  
可以看出,withInitial()方法的入參是函數式接口Supplier,返回值是JDK8新增的內部類SuppliedThreadLocal,正如2.2所說,區別僅在於支持Lambda表達式賦值而已。使用事例如下:
  1. @Test  
  2. public void jdk8Test(){  
  3.     Supplier<String> supplier =new Supplier<String>(){  
  4.          @Override  
  5.        public String get(){  
  6.            return"supplier_new";  
  7.        }  
  8.    };  
  9.     threadLocal= ThreadLocal.withInitial(supplier);  
  10.     System.out.println(threadLocal.get());// supplier_new  
  11.     threadLocal= ThreadLocal.withInitial(()->"sup_new_2");  
  12.     System.out.println(threadLocal.get());// sup_new_2  
  13.     ThreadLocal<DateFormat> localDate = ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd"));  
  14.     System.out.println(localDate.get().format(new Date()));// 2017-01-22  
  15.     ThreadLocal<String> local =new ThreadLocal<>().withInitial(supplier);  
  16.     System.out.println(local.get());// supplier_new  
  17. }  
Note:
·withInitial(supplier)是有返回值ThreadLocal的,So實例化時需將其賦值給ThreadLocal實例。

4、圖解 ThreadLocal
每個線程可能有多個ThreadLocal,同一線程的各個ThreadLocal存放於同一個ThreadLocalMap中。

圖解ThreadLocal(JDK8).vsdx原圖下載地址: https://github.com/zxiaofan/JDK-Study/tree/master/src/java1/lang/threadLocal


5、 ThreadLocal-ThreadLocalMap源碼分析

5.1、Entry getEntry(ThreadLocal<?> key)
首先來看get方法,你會發現ThreadLocalMap的get方法和傳統Map不同,其返回的不是key-value的value,而是整個entry,當時entry的key是ThreadLocal,value是存放的值,這點是一致的。
a、getEntry源碼分析:
  1. private Entry getEntry(ThreadLocal<?> key){  
  2.    int i= key.threadLocalHashCode&(table.length-1);  
  3.     Entry e= table[i];  
  4.    if(e!=null&& e.get()== key)  
  5.        return e;  
  6.    else  
  7.        return getEntryAfterMiss(key, i, e);  
  8. }  
getEnrty方法只會處理key被直接命中的entry,沒有直接命中的(key衝突的)數據將調用getEntryAfterMiss()方法返回對應enrty,按照源碼解釋,這樣做是爲了儘可能提升直接命中的性能。
ThreadLocalMap之getEntry的流程:
1、計算Entry數組的index((length - 1) & key.hash)。
索引計算和HashMap的異同:
①相似之處:計算方式相同,均爲(length - 1) & key.hash;length均爲底層結構的大小(是大小,不是實際size)。
②不同之處:HashMap(JDK8)底層數據結構是 位桶+鏈表/紅黑樹,而ThreadLocalMap底層數據結構是 Entry數組;HashMap的key.hash的計算方式是 native、異或、無符號位移,(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),ThreadLocalMap的key.hash從ThreadLocal實例化時便由nextHashCode()確定。
2、獲取對應index的節點Entry;
3、如果返回節點entry 有值且其key未衝突(只有1個即entry返回的key等於傳入的key),則直接返回該entry;
4、返回entry爲空或鍵衝突,則調用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法返回entry。

b、getEntryAfterMiss源碼分析:
getEntryAfterMiss處理那些getEntry時沒有被命中的key(value爲空的直接返回null,so更確切的說是命中且有衝突的key)。入參是當前ThreadLocal,key在數組的索引index,以及index對應的鍵值對。
  1. private Entry getEntryAfterMiss(ThreadLocal<?> key,int i, Entry e){  
  2.     Entry[] tab = table;  
  3.    int len= tab.length;  
  4.     while(e!=null){  
  5.         ThreadLocal<?> k = e.get();  
  6.        if(k== key)  
  7.            return e;  
  8.        if(k==null)  
  9.             expungeStaleEntry(i);  
  10.        else  
  11.             i= nextIndex(i, len);  
  12.         e= tab[i];  
  13.    }  
  14.    return null;  
  15. }  
ThreadLocalMap之getEntryAfterMiss的流程:
僅分析Entry不爲空的情況,
1、獲取entry的key;
2、如果key一致(內存地址=判斷),則返回該entry;
3、如果key爲null,則調用expungeStaleEntry方法 擦除該entry;
4、其他情況則通過nextIndex方法獲取下一個索引位置index;
5、獲取新index處的entry,再死循環2/3/4,直到定位到該key返回entry或者返回null。
  1. private static int nextIndex(int i,int len){  
  2.    return((i+1< len)? i+1:0); // 把索引加1即可  
  3. }  
c、expungeStaleEntry源碼分析:
只要key爲null均會被擦除,使得對應value沒有被引用,方便回收。
  1. private int expungeStaleEntry(int staleSlot){  
  2.     Entry[] tab = table;  
  3.    int len= tab.length;  
  4.     // expunge entry at staleSlot  
  5.     tab[staleSlot].value=null// 擦除當前index處value  
  6.     tab[staleSlot]=null// 擦除當前index處key  
  7.     size--;  
  8.     // Rehash until we encounter null  
  9.     Entry e;  
  10.    int i;  
  11.    for(i= nextIndex(staleSlot, len); // 計算下一個index  
  12.         (e= tab[i])!=null// 新index處entry不爲空  
  13.          i= nextIndex(i, len)){ // 計算下一個index  
  14.         ThreadLocal<?> k = e.get(); // 獲取新key(ThreadLocal)  
  15.        if(k==null){ // key爲null,再次置空  
  16.             e.value=null;  
  17.             tab[i]=null;  
  18.             size--;  
  19.        }else{  
  20.            int h= k.threadLocalHashCode&(len-1); // 計算新index  
  21.            if(h!= i){ // index若未變化,說明沒有多餘的entry了  
  22.                 tab[i]=null;  
  23.                 // Unlike Knuth 6.4 Algorithm R, we must scan until  
  24.                // null because multiple entries could have been stale.  
  25. // 一直掃到最後一個非空位置,將其值置爲碰撞處第一個entry。  
  26.                while(tab[h]!=null)  
  27.                     h= nextIndex(h, len);  
  28.                 tab[h]= e;  
  29.            }  
  30.        }  
  31.    }  
  32.    return i;  
  33. }  


5.2、set(ThreadLocal<?> key, Object value)
  1. private void set(ThreadLocal<?> key, Object value){  
  2.     // We don't use a fast path as with get() because it is at  
  3.    // least as common to use set() to create new entries as  
  4.    // it is to replace existing ones, in which case, a fast  
  5.    // path would fail more often than not.  
  6.     Entry[] tab = table;  
  7.    int len= tab.length;  
  8.    int i= key.threadLocalHashCode&(len-1);  
  9.     for(Entry e= tab[i]; e !=null;e= tab[i= nextIndex(i, len)]){  
  10. // 當前index處已有entry  
  11.         ThreadLocal<?> k = e.get();  
  12.         if(k== key){ // key(ThreadLocal)相同,更新value  
  13.             e.value= value;  
  14.            return;  
  15.        }  
  16.         if(k==null){ // 出現過期數據  
  17. // 遍歷清洗過期數據並在index處插入新數據,其他數據後移  
  18.             replaceStaleEntry(key, value, i);  
  19.            return;  
  20.        }  
  21.    }  
  22.     tab[i]=new Entry(key, value);  
  23.    int sz=++size;  
  24. // 沒有過期數據被清理且實際size超過擴容閾值  
  25.    if(!cleanSomeSlots(i, sz)&& sz>= threshold)  
  26.         rehash();  
  27. }  

rehash():
size:table的實際entry數量;擴容閾值threshold:table.lenrth(默認16)大小的2/3;
首先調用expungeStaleEntries刪除所有過期數據,如果清理數據後size>=threshold的3/4,則2倍擴容。
ps:閾yù值又叫臨界值,是指一個效應能夠產生的最低值或最高值。閥fá 控制、開關、把持。

ThreadLocalMap和HashMap在hash衝突時的解決方案對比:
HashMap:若衝突則將新數據按鏈表或紅黑樹邏輯插入。
put(K key, V value)的邏輯:
1、判斷鍵值對數組tab[]是否爲空或爲null,是則resize(); 
2、根據鍵值key的hashCode()計算hash值得到當前Node的索引i( bucketIndex ),如果tab[i]==null【沒碰撞】,直接新建節點添加,否則【碰撞】轉入3 
3、判斷當前數組中處理hash衝突的方式爲紅黑樹還是鏈表(check第一個節點類型即可),分別處理。【①是紅黑樹則按紅黑樹邏輯插入;②是鏈表,則遍歷鏈表,看是否有key相同的節點;③有則更新value值,沒有則新建節點,此時若 鏈表數量大於閥值8【9個】,則調用treeifyBin方法(此方法先判斷table是否爲null或tab.length小於64,是則執行resize操作,否則纔將鏈表改爲紅黑樹)。】
4、如果size+1> threshold則resize。

ThreadLocalMap:
1、若指定位置index已有數據entry,逐個遍歷entry:
1.1、若index處key相同,則更新value;
1.2、若index處key爲null,則調用replaceStaleEntry清理過期數據並插入新數據(從index處挨個遍歷,直到找到相同key更新value結束,或者一直未找到,則在index處放入new Entry)。replaceStaleEntry遍歷時會將entry逐個後移,也就是說set進去的最新entry一定會放在index處,方便get時直接命中。
2、index處無數據,則放入新entry;隨後清理過期數據並判斷是否2倍擴容(size>=threshold的3/4)。

參考資料:
http://www.cnblogs.com/dolphin0520/p/3920407.html
http://blog.csdn.net/u010887744/article/details/54730556
有任何問題,歡迎指正探討。
參考
[javascript]  view plain  copy
  1. 歡迎個人轉載,但須在文章頁面明顯位置給出原文連接;  
  2. 未經作者同意必須保留此段聲明、不得隨意修改原文、不得用於商業用途,否則保留追究法律責任的權利。  
  3.   
  4. 【 CSDN 】:csdn.zxiaofan.com  
  5. 【GitHub】:github.zxiaofan.com  
  6.   
  7. 如有任何問題,歡迎留言。祝君好運!  
  8. Life is all about choices!   
  9. 將來的你一定會感激現在拼命的自己!  
ThreadLocal特性及使用場景:
1、方便同一個線程使用某一對象,避免不必要的參數傳遞;
2、線程間數據隔離(每個線程在自己線程裏使用自己的局部變量,各線程間的ThreadLocal對象互不影響);
3、獲取數據庫連接、Session、關聯ID(比如日誌的uniqueID,方便串起多個日誌);

ThreadLocal應注意:
1、ThreadLocal並未解決多線程訪問共享對象的問題;
2、ThreadLocal並不是每個線程拷貝一個對象,而是直接new(新建)一個;
3、如果ThreadLocal.set()的對象是多線程共享的,那麼還是涉及併發問題。
1、ThreadLocal<T>初始化
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT= 0x61c88647;
/**
* The next hash code to be given out. Updated atomically. Starts at zero.
*/
// 源碼說nextHashCode初始值爲0,但實際調試時顯示初始值爲1253254570,費解?
// 而且當初始化完畢後,nextHashCode的值又變爲0,說明其初始值確實是0的。
  1. private static AtomicInteger nextHashCode = new AtomicInteger();  
  2. private static intnextHashCode() {  
  3.     return nextHashCode.getAndAdd(HASH_INCREMENT);  
  4. }  
ThreadLocal類變量有3個,其中2個是靜態變量(包括一個常量),實際作爲作爲ThreadLocal實例的變量只有threadLocalHashCode這1個,而且已經初始化就不可變了。
創建ThreadLocal實例時有哪些操作呢:
ThreadLocal初始化時會調用nextHashCode()方法初始化threadLocalHashCode,且threadLocalHashCode 初始化後不可變threadLocalHashCode可用來標記不同的ThreadLocal實例。


2、內部類
2.1 ThreadLocalMap
ThreadLocalMap是 定製的hashMap,僅用於維護當前線程的本地變量值。僅ThreadLocal類對其有操作權限,是Thread的私有屬性。爲避免佔用空間較大或生命週期較長的數據常駐於內存引發一系列問題,hash table的key是弱引用WeakReferences。當空間不足時,會清理未被引用的entry。
ThreadLocalMap中的重點:
  1. static class Entryextends WeakReference<ThreadLocal<?>>{  
  2.    /** The value associated with this ThreadLocal. */  
  3.     Object value;  
  4.      Entry(ThreadLocal<?> k, Object v){  
  5.        super(k);  
  6.         value= v;  
  7.    }  
  8. }  
Note:
ThreadLocalMap的key是ThreadLocal,value是Object(即我們所謂的「線程本地數據」)。

2.2 SuppliedThreadLocal<T> extends ThreadLocal<T>
SuppliedThreadLocal是JDK8新增的內部類,只是擴展了ThreadLocal的初始化值的方法而已,允許使用JDK8新增的Lambda表達式賦值。需要注意的是,函數式接口Supplier不允許爲null。
源碼如下:
  1. static final class SuppliedThreadLocal<T>extends ThreadLocal<T>{  
  2.     private final Supplier<?extends T> supplier;  
  3.      SuppliedThreadLocal(Supplier<?extends T> supplier){  
  4.        this.supplier= Objects.requireNonNull(supplier);  
  5.    }  
  6.      @Override  
  7.    protected T initialValue(){  
  8.        return supplier.get();  
  9.    }  
  10. }  

3、主要方法
3.1、T get()
返回當前線程的value。
  1. public T get(){  
  2.     Thread t= Thread.currentThread(); // 獲取當前線程  
  3.     ThreadLocalMap map= getMap(t); // 獲取當前線程對應的Map  
  4.    if(map!=null){  
  5.         ThreadLocalMap.Entry e = map.getEntry(this); // 詳見3.1.1  
  6.        if(e!=null){ // map不爲空且當前線程有value,返回value  
  7.             @SuppressWarnings("unchecked")  
  8.             T result=(T)e.value;  
  9.            return result;  
  10.        }  
  11.    }  
  12.    return setInitialValue(); // 初始化再返回值  
  13. }  
-----
getMap的源碼:
  1. ThreadLocalMap getMap(Thread t){  
  2.    returnt.threadLocals;  
  3. }  
getMap(t)返回當前線程的成員變量ThreadLocalMap(Thread的成員變量有ThreadLocalMap,這一點可以查看Thread的源碼,如下)很明確的說明了 ThreadLocal屬於線程,ThreadLocalMap由ThreadLocal持有,說到底,ThreadLocalMap 也是線程所持有。每個線程Thread都有自己的ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

--------
setInitialValue源碼:
  1. private T setInitialValue(){  
  2.     T value= initialValue(); //調用重寫的initialValue,返回新值  
  3.     Thread t= Thread.currentThread();  
  4.     ThreadLocalMap map= getMap(t);  
  5.    if(map!=null// 當前線程的ThreadLocalMap不爲空,則直接賦值  
  6.         map.set(this, value);  
  7.    else  
  8. // 爲當前線程創造一個ThreadLocalMap(this, firstValue)並賦初值,this爲當前線程  
  9.         createMap(t, value);  
  10.    return value;  
  11. }  
  12. protected T initialValue() {  
  13.     return T; // 自定義返回值  
  14. };  
createMap源碼:
  1. void createMap(Thread t, T firstValue){  
  2.     t.threadLocals=new ThreadLocalMap(this, firstValue);  
  3. }  
ThreadLocal之get流程:
1、獲取當前線程t;
2、返回當前線程t的成員變量ThreadLocalMap(以下簡寫map);
3、map不爲null,則獲取以當前線程爲key的ThreadLocalMap的Entry(以下簡寫e),如果e不爲null,則直接返回該Entry的value;
4、如果map爲null或者e爲null,返回setInitialValue()的值。setInitialValue()調用重寫的 initialValue()返回新值(如果沒有重寫initialValue將返回默認值null ),並將新值存入當前線程的ThreadLocalMap(如果當前線程沒有ThreadLocalMap,會先創建一個)。


3.2、void set(T value)
爲【當前線程】的【當前ThreadLocal】賦值(初始值or新值)。和setInitialValue相當相似,就不多分析了。
  1. public void set(T value){  
  2.     Thread t= Thread.currentThread();  
  3.     ThreadLocalMap map= getMap(t);  
  4.    if(map!=null)  
  5.         map.set(this, value);  
  6.    else  
  7.         createMap(t, value);  
  8. }  

3.3、void remove()
獲取當前線程的ThreadLocalMap,map不爲空,則移除當前ThreadLocal作爲key的鍵值對。
  1. public void remove(){  
  2.      ThreadLocalMap m= getMap(Thread.currentThread());  
  3.     if(m!=null)  
  4.          m.remove(this);  
  5.  }  
Note:
remove()移除當前線程的當前ThreadLocal數據(只是清空該key-value鍵值對),而且是 立即移除,移除後,再調用get方法將重新調用initialValue方法初始化(除非在此期間調用了set方法賦值)。


3.4、static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
JDK8新增,支持Lambda表達式,和 ThreadLocal重寫的initialValue()效果一樣。
  1. public static<S> ThreadLocal<S> withInitial(Supplier<?extends S> supplier){  
  2.    return new SuppliedThreadLocal<>(supplier);  
  3. }  
可以看出,withInitial()方法的入參是函數式接口Supplier,返回值是JDK8新增的內部類SuppliedThreadLocal,正如2.2所說,區別僅在於支持Lambda表達式賦值而已。使用事例如下:
  1. @Test  
  2. public void jdk8Test(){  
  3.     Supplier<String> supplier =new Supplier<String>(){  
  4.          @Override  
  5.        public String get(){  
  6.            return"supplier_new";  
  7.        }  
  8.    };  
  9.     threadLocal= ThreadLocal.withInitial(supplier);  
  10.     System.out.println(threadLocal.get());// supplier_new  
  11.     threadLocal= ThreadLocal.withInitial(()->"sup_new_2");  
  12.     System.out.println(threadLocal.get());// sup_new_2  
  13.     ThreadLocal<DateFormat> localDate = ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd"));  
  14.     System.out.println(localDate.get().format(new Date()));// 2017-01-22  
  15.     ThreadLocal<String> local =new ThreadLocal<>().withInitial(supplier);  
  16.     System.out.println(local.get());// supplier_new  
  17. }  
Note:
·withInitial(supplier)是有返回值ThreadLocal的,So實例化時需將其賦值給ThreadLocal實例。

4、圖解 ThreadLocal
每個線程可能有多個ThreadLocal,同一線程的各個ThreadLocal存放於同一個ThreadLocalMap中。

圖解ThreadLocal(JDK8).vsdx原圖下載地址: https://github.com/zxiaofan/JDK-Study/tree/master/src/java1/lang/threadLocal


5、 ThreadLocal-ThreadLocalMap源碼分析

5.1、Entry getEntry(ThreadLocal<?> key)
首先來看get方法,你會發現ThreadLocalMap的get方法和傳統Map不同,其返回的不是key-value的value,而是整個entry,當時entry的key是ThreadLocal,value是存放的值,這點是一致的。
a、getEntry源碼分析:
  1. private Entry getEntry(ThreadLocal<?> key){  
  2.    int i= key.threadLocalHashCode&(table.length-1);  
  3.     Entry e= table[i];  
  4.    if(e!=null&& e.get()== key)  
  5.        return e;  
  6.    else  
  7.        return getEntryAfterMiss(key, i, e);  
  8. }  
getEnrty方法只會處理key被直接命中的entry,沒有直接命中的(key衝突的)數據將調用getEntryAfterMiss()方法返回對應enrty,按照源碼解釋,這樣做是爲了儘可能提升直接命中的性能。
ThreadLocalMap之getEntry的流程:
1、計算Entry數組的index((length - 1) & key.hash)。
索引計算和HashMap的異同:
①相似之處:計算方式相同,均爲(length - 1) & key.hash;length均爲底層結構的大小(是大小,不是實際size)。
②不同之處:HashMap(JDK8)底層數據結構是 位桶+鏈表/紅黑樹,而ThreadLocalMap底層數據結構是 Entry數組;HashMap的key.hash的計算方式是 native、異或、無符號位移,(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),ThreadLocalMap的key.hash從ThreadLocal實例化時便由nextHashCode()確定。
2、獲取對應index的節點Entry;
3、如果返回節點entry 有值且其key未衝突(只有1個即entry返回的key等於傳入的key),則直接返回該entry;
4、返回entry爲空或鍵衝突,則調用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法返回entry。

b、getEntryAfterMiss源碼分析:
getEntryAfterMiss處理那些getEntry時沒有被命中的key(value爲空的直接返回null,so更確切的說是命中且有衝突的key)。入參是當前ThreadLocal,key在數組的索引index,以及index對應的鍵值對。
  1. private Entry getEntryAfterMiss(ThreadLocal<?> key,int i, Entry e){  
  2.     Entry[] tab = table;  
  3.    int len= tab.length;  
  4.     while(e!=null){  
  5.         ThreadLocal<?> k = e.get();  
  6.        if(k== key)  
  7.            return e;  
  8.        if(k==null)  
  9.             expungeStaleEntry(i);  
  10.        else  
  11.             i= nextIndex(i, len);  
  12.         e= tab[i];  
  13.    }  
  14.    return null;  
  15. }  
ThreadLocalMap之getEntryAfterMiss的流程:
僅分析Entry不爲空的情況,
1、獲取entry的key;
2、如果key一致(內存地址=判斷),則返回該entry;
3、如果key爲null,則調用expungeStaleEntry方法 擦除該entry;
4、其他情況則通過nextIndex方法獲取下一個索引位置index;
5、獲取新index處的entry,再死循環2/3/4,直到定位到該key返回entry或者返回null。
  1. private static int nextIndex(int i,int len){  
  2.    return((i+1< len)? i+1:0); // 把索引加1即可
    5、獲取新index處的entry,再死循環2/3/4,直到定位到該key返回entry或者返回null。
    1. private static int nextIndex(int i,int len){  
    2.    return((i+1< len)? i+1:0); // 把索引加1即可  
    3. }  
    c、expungeStaleEntry源碼分析:
    只要key爲null均會被擦除,使得對應value沒有被引用,方便回收。
    1. private int expungeStaleEntry(int staleSlot){  
相關文章
相關標籤/搜索