ThreadLocal終極源碼剖析-一篇足矣!

本文較深刻的分析了ThreadLocal和InheritableThreadLocal,從4個方向去分析:源碼註釋、源碼剖析、功能測試、應用場景。html

1、ThreadLocal

咱們使用ThreadLocal解決線程局部變量統必定義問題,多線程數據不能共享。(InheritableThreadLocal特例除外)不能解決併發問題。解決了:基於類級別的變量定義,每個線程單獨維護本身線程內的變量值(存、取、刪的功能java

根據源碼,畫出原理圖以下:算法

注意點:數組

1.ThreadLocal類封裝了getMap()、Set()、Get()、Remove()4個核心方法。緩存

2.經過getMap()獲取每一個子線程Thread持有本身的ThreadLocalMap實例, 所以它們是不存在併發競爭的。能夠理解爲每一個線程有本身的變量副本。
安全

3.ThreadLocalMap中Entry[]數組存儲數據,初始化長度16,後續每次都是2倍擴容。主線程中定義了幾個變量,Entry[]纔有幾個key。session

4.Entry的key是對ThreadLocal的弱引用,當拋棄掉ThreadLocal對象時,垃圾收集器會忽略這個key的引用而清理掉ThreadLocal對象, 防止了內存泄漏。數據結構

1.1源碼註釋

理解原理最好的方法是看源碼註釋:多線程

1 This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). 
2 
3 For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls. 

這個類提供線程局部變量。這些變量與正常的變量不一樣,每一個線程訪問一個(經過它的get或set方法)都有它本身的、獨立初始化的變量副本。ThreadLocal實例一般是類中的私有靜態字段,但願將狀態與線程關聯(例如,用戶ID或事務ID)。併發

註釋中的示例代碼:

下圖ThreadId類會在每一個線程中生成惟一標識符。線程的id在第一次調用threadid.get()時被分配,在隨後的調用中保持不變。

 ThreadId類利用AtomicInteger原子方法getAndIncrement,爲每一個線程建立一個threadId變量,例如第一個線程是1,第二個線程是2...,並提供一個類靜態get方法用以獲取當前線程ID。:

 1 import java.util.concurrent.atomic.AtomicInteger;
 2 
 3  public class ThreadId {
 4      // Atomic integer containing the next thread ID to be assigned
 5      private static final AtomicInteger nextId = new AtomicInteger(0);
 6 
 7      // Thread local variable containing each thread's ID
 8      private static final ThreadLocal<Integer> threadId =
 9          new ThreadLocal<Integer>() {
10              @Override protected Integer initialValue() {
11                  return nextId.getAndIncrement();
12          }
13      };
14 
15      // Returns the current thread's unique ID, assigning it if necessary
16      public static int get() {
17          return threadId.get();
18      }
19  }

如上圖,有一個注意點是:用戶能夠自定義initialValue()初始化方法,來初始化threadLocal的值。

1.2 源碼剖析

咱們來追蹤一下ThreadLocal源碼:

 1 public T get() {
 2         Thread t = Thread.currentThread();
 3         ThreadLocalMap map = getMap(t);
 4         if (map != null) {
 5             ThreadLocalMap.Entry e = map.getEntry(this);
 6             if (e != null) {
 7                 @SuppressWarnings("unchecked")
 8                 T result = (T)e.value;
 9                 return result;
10             }
11         }
12         return setInitialValue();
13     }
14 
21     private T setInitialValue() {
22         T value = initialValue();
23         Thread t = Thread.currentThread();
24         ThreadLocalMap map = getMap(t);
25         if (map != null)
26             map.set(this, value);
27         else
28             createMap(t, value);
29         return value;
30     }
31 
41     public void set(T value) {
42         Thread t = Thread.currentThread();
43         ThreadLocalMap map = getMap(t);
44         if (map != null)
45             map.set(this, value);
46         else
47             createMap(t, value);
48     }
49 
61      public void remove() {
62          ThreadLocalMap m = getMap(Thread.currentThread());
63          if (m != null)
64              m.remove(this);
65      }
66 
74     ThreadLocalMap getMap(Thread t) {
75         return t.threadLocals;
76     }

看源碼咱們知道不論是set、get、remove操做的都是ThreadLocalMap,key=當前線程,value=線程局部變量緩存值。

上圖getMap最終調用的Thread的成員變量 ThreadLocal.ThreadLocalMap threadLocals,以下圖:

ThreadLocalMap是ThreadLocal的一個內部類,源碼註釋:

ThreadLocalMap是一個定製的哈希映射,僅適用於維護線程本地值。ThreadLocalMap類是包私有的,容許在Thread類中聲明字段。爲了幫助處理很是大且長時間的使用,哈希表entry使用了對鍵的弱引用。有助於GC回收。

散列算法-魔數0x61c88647

 ThreadLocal中定義了一個AtomicInteger,一個魔數0x61c88647,利用必定算法實現了元素的完美散列。

源碼中元素散列算法以下:

1.求hashCode = i*HASH_INCREMENT+HASH_INCREMENT每次新增一個元素(threadLocal)進Entry[],自增0x61c88647
2.元素散列位置(數組下標)= hashCode & (length-1),

下面校驗算法的散列性:

 1 /**
 2  * 
 3  * @ClassName:MagicHashCode
 4  * @Description:ThreadLocalMap使用「開放尋址法」中最簡單的「線性探測法」解決散列衝突問題
 5  * @author diandian.zhang
 6  * @date 2017年12月6日上午10:53:28
 7  */
 8 public class MagicHashCode {
 9     //ThreadLocal中定義的hash魔數
10     private static final int HASH_INCREMENT = 0x61c88647;
11     
12     public static void main(String[] args) {
13         hashCode(16);//初始化16
14         hashCode(32);//後續2倍擴容
15         hashCode(64);
16     }
17 
18     /**
19      * 
20      * @Description 尋找散列下標(對應數組小標)
21      * @param length table長度
22      * @author diandian.zhang
23      * @date 2017年12月6日上午10:36:53
24      * @since JDK1.8
25      */
26     private static void hashCode(Integer length){
27         int hashCode = 0; 
28         for(int i=0;i<length;i++){
29             hashCode = i*HASH_INCREMENT+HASH_INCREMENT;//每次遞增HASH_INCREMENT
30             System.out.print(hashCode & (length-1));//求散列下標,算法公式
31             System.out.print(" ");
32         }
33         System.out.println();
34     }
35 }

運行結果:

7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 --》Entry[]初始化容量爲16時,元素完美散列  
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0--》Entry[]容量擴容2倍=32時,元素完美散列
7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 16 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0 --》Entry[]容量擴容2倍=64時,元素完美散列

根據運行結果,表明此算法在長度爲2的N次方的數組上,確實能夠完美散列

那麼原理是什麼?

long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));//(根號5-1)*2的31次方=(根號5-1)/2 *2的32次方=黃金分割數*2的32次方
System.out.println("as 32 bit unsigned: " + l1);//32位無符號整數
int i1 = (int) l1;
System.out.println("as 32 bit signed:   " + i1);//32位有符號整數
System.out.println("MAGIC = " + 0x61c88647);

運行結果:

as 32 bit unsigned: 2654435769
as 32 bit signed:   -1640531527
MAGIC = 1640531527

這裏再也不拓展,跟斐波那契數列(和黃金分割數)有關:

1.0x61c88647對應十進制=1640531527。

2.(根號5-1)*2的31次方,轉換成long類型就是2654435769,轉換成int類型就是-1640531527。

set操做

ThreadLocal的set最終調用了ThreadLocalMap的set方法,以下圖

 1  private void set(ThreadLocal<?> key, Object value) {
 8             Entry[] tab = table;
 9             int len = tab.length;
10             int i = key.threadLocalHashCode & (len-1);哈希碼和數組長度求元素放置的位置,即數組下標 11             //從i開始日後一直遍歷到數組最後一個Entry
12             for (Entry e = tab[i];
13                  e != null;
14                  e = tab[i = nextIndex(i, len)]) {
15                 ThreadLocal<?> k = e.get();
16                 //若是key相等,覆蓋value
17                 if (k == key) {
18                     e.value = value;
19                     return;
20                 }
21                 //若是key爲null,用新key、value覆蓋,同時清理歷史key=null的陳舊數據
22                 if (k == null) {
23                     replaceStaleEntry(key, value, i);
24                     return;
25                 }
26             }
27 
28             tab[i] = new Entry(key, value);
29             int sz = ++size;
//若是超過閥值,就須要再哈希了
30 if (!cleanSomeSlots(i, sz) && sz >= threshold) 31 rehash(); 32 }// 根據

再哈希:

 1      private void rehash() {
 2  expungeStaleEntries();// 清理一次陳舊數據
 3 
 4             // 清理完陳舊數據,若是>= 3/4閥值,就執行擴容,避免遲滯
 5             if (size >= threshold - threshold / 4)
 6  resize();
 7         }
 8 
 9         /**
10          * 把table擴容2倍,並把老數據從新哈希散列進新table
11          */
12         private void resize() {
13             Entry[] oldTab = table;
14             int oldLen = oldTab.length;
15             int newLen = oldLen * 2;
16             Entry[] newTab = new Entry[newLen];
17             int count = 0;
18             // 遍歷Entry[]數組
19             for (int j = 0; j < oldLen; ++j) {
20                 Entry e = oldTab[j];
21                 if (e != null) {
22                     ThreadLocal<?> k = e.get();
23                     if (k == null) {// 若是key=null
24                         e.value = null; // 把value也置null,有助於GC回收對象
25                     } else {// 若是key!=null
26                         int h = k.threadLocalHashCode & (newLen - 1);// 計算hash值 
27                         while (newTab[h] != null)// 若是這個位置已使用
28                             h = nextIndex(h, newLen);// 線性日後查詢,直到找到一個沒有使用的位置,h遞增
29 newTab[h] = e;//在第一個空節點上塞入Entry e 30 count++;// 計數++ 31 } 32 } 33 } 34 35 setThreshold(newLen);// 設置新的閾值(實際set方法用了2/3的newLen做爲閾值) 36 size = count;// 設置ThreadLocalMap的元素個數 37 table = newTab;// 把新table賦值給ThreadLocalMap的Entry[] table 38 } 39 40 /** 41 * 刪除陳舊的數據 42 */ 43 private void expungeStaleEntries() { 44 Entry[] tab = table; 45 int len = tab.length; 46 for (int j = 0; j < len; j++) { 47 Entry e = tab[j]; 48 if (e != null && e.get() == null)//entry不爲空且entry的key爲null 49 expungeStaleEntry(j);//刪除指定數組下標的陳舊entry 50 } 51 } 52 //刪除陳舊entry的核心方法 53 private int expungeStaleEntry(int staleSlot) { 54 Entry[] tab = table; 55 int len = tab.length; 56 57 58 tab[staleSlot].value = null;//刪除value 59 tab[staleSlot] = null;//刪除entry 60 size--;//map的size自減 61 62 // 遍歷指定刪除節點,全部後續節點 63 Entry e; 64 int i; 65 for (i = nextIndex(staleSlot, len); 66 (e = tab[i]) != null; 67 i = nextIndex(i, len)) { 68 ThreadLocal<?> k = e.get(); 69 if (k == null) {//key爲null,執行刪除操做 70 e.value = null; 71 tab[i] = null; 72 size--; 73 } else {//key不爲null,從新計算下標 74 int h = k.threadLocalHashCode & (len - 1); 75 if (h != i) {//若是不在同一個位置 76 tab[i] = null;//把老位置的entry置null(刪除) 77 78 // 從h開始日後遍歷,一直到找到空爲止,插入 80 while (tab[h] != null) 81 h = nextIndex(h, len); 82 tab[h] = e; 83 } 84 } 85 } 86 return i; 87 }

總結set步驟:

1)根據哈希碼和數組長度求元素放置的位置,即數組下標

2)從第一步得出的下標開始日後遍歷,若是key相等,覆蓋value,若是key爲null,用新key、value覆蓋,同時清理歷史key=null的陳舊數據

3)若是超過閥值,就須要再哈希:

  • 清理一遍陳舊數據 
  • >= 3/4閥值,就執行擴容,把table擴容2倍==》注意這裏3/4閥值就執行擴容,避免遲滯
  • 把老數據從新哈希散列進新table

 get操做

 1   public T get() {
 2         Thread t = Thread.currentThread();
 3         ThreadLocalMap map = getMap(t);//從當前線程中獲取ThreadLocalMap
 4         if (map != null) {
 5             ThreadLocalMap.Entry e = map.getEntry(this);//查詢當前ThreadLocal變量實例對應的Entry
 6             if (e != null) {//若是不爲null,獲取value,返回
 7                 @SuppressWarnings("unchecked")
 8                 T result = (T)e.value;
 9                 return result;
10             }
11         }//若是map爲null,即尚未初始化,走初始化方法
12         return setInitialValue();
13     }
14 
21     private T setInitialValue() {
22         T value = initialValue();//該方法默認返回null,用戶可自定義
23         Thread t = Thread.currentThread();
24         ThreadLocalMap map = getMap(t);
25         if (map != null)//若是map不爲null,把初始化value設置進去
26             map.set(this, value);
27         else//若是map爲null,則new一個map,並把初始化value設置進去
28  createMap(t, value);
29         return value;
30     }
31 
32     void createMap(Thread t, T firstValue) {
33         t.threadLocals = new ThreadLocalMap(this, firstValue);
34     }
35 
36     ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
37     table = new Entry[INITIAL_CAPACITY];//初始化容量16
38     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
39     table[i] = new Entry(firstKey, firstValue);
40     size = 1;
41  setThreshold(INITIAL_CAPACITY);//設置閾值
42     }
43     //閾值設置爲容量的*2/3,即負載因子爲2/3,超過就進行再哈希
44     private void setThreshold(int len) {
45         threshold = len * 2 / 3;
46      }

總結get步驟:

1)從當前線程中獲取ThreadLocalMap,查詢當前ThreadLocal變量實例對應的Entry,若是不爲null,獲取value,返回

2)若是map爲null,即尚未初始化,走初始化方法

remove操做

 1 public void remove() {
 2     ThreadLocalMap m = getMap(Thread.currentThread());
 3     if (m != null)
 4         m.remove(this);//調用ThreadLocalMap刪除變量
 5 }
 6 
 7 private void remove(ThreadLocal<?> key) {
 8     Entry[] tab = table;
 9     int len = tab.length;
10     int i = key.threadLocalHashCode & (len-1);
11     for (Entry e = tab[i];
12          e != null;
13          e = tab[i = nextIndex(i, len)]) {
14         if (e.get() == key) {
15             e.clear();//調用Entry的clear方法
16  expungeStaleEntry(i);//清除陳舊數據
17             return;
18         }
19     }
20 }

看一下Entry的clear方法,Entry ==extends==》 WeakReference<ThreadLocal<?>>==extends==》 Reference<T>,clear方法是抽象類Reference定義的方法。

1 static class Entry extends WeakReference<ThreadLocal<?>> {
2     /** The value associated with this ThreadLocal. */
3     Object value;
4 
5     Entry(ThreadLocal<?> k, Object v) {
6         super(k);
7         value = v;
8     }
9 }
追一下clear方法以下:把弱引用的對象置null。有利於GC回收內存。關於引用,預留飛機票
public void clear() {
    this.referent = null;
}

1.3 功能測試

開啓2個線程,每一個個線程都使用類級別的threadLocal,往裏面遞增數字,i=0,時,set(0),i=1,2,3時 值+1,

 1 /**  2  *  3  * @ClassName:MyThreadLocal  4  * @Description:ThreadLocal線程本地變量  5  * @author diandian.zhang  6  * @date 2017年12月4日上午9:40:52  7 */  8 public class MyThreadLocal{  9 //線程本地共享變量 10 private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){ 11 /** 12  * ThreadLocal沒有被當前線程賦值時或當前線程剛調用remove方法後調用get方法,返回此方法值 13 */ 14  @Override 15 protected Object initialValue() 16  { 17 System.out.println("[線程"+Thread.currentThread().getName()+"]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值!"); 18 return null; 19  } 20  }; 21 22 public static void main(String[] args){ 23 //1.開啓任務1線程 24 new Thread(new MyIntegerTask("IntegerTask1")).start(); 25 //2.中間休息3秒,用以測試數據差別 26 try { 27 Thread.sleep(3000); 28 } catch (InterruptedException e) { 29  e.printStackTrace(); 30  } 31 //3.開啓任務2線程 32 new Thread(new MyIntegerTask("IntegerTask2")).start(); 33  } 34 35 /** 36  * 37  * @ClassName:MyIntegerTask 38  * @Description:整形遞增線程 39  * @author diandian.zhang 40  * @date 2017年12月4日上午10:00:41 41 */ 42 public static class MyIntegerTask implements Runnable{ 43 private String name; 44 45  MyIntegerTask(String name) 46  { 47 this.name = name; 48  } 49 50  @Override 51 public void run() 52  { 53 for(int i = 0; i < 5; i++) 54  { 55 // ThreadLocal.get方法獲取線程變量 56 if(null == MyThreadLocal.threadLocal.get()) 57  { 58 // ThreadLocal.set方法設置線程變量 59 MyThreadLocal.threadLocal.set(0); 60 System.out.println("i="+i+"[線程" + name + "]當前線程不存在緩存,set 0"); 61  } 62 else 63  { 64 int num = (Integer)MyThreadLocal.threadLocal.get(); 65 MyThreadLocal.threadLocal.set(num + 1); 66 System.out.println("i="+i+"[線程" + name + "]往threadLocal中set: " + MyThreadLocal.threadLocal.get()); 67 //當i=3即循環4次時,移除當前線程key 68 if(i == 3) 69  { 70 System.out.println("i="+i+"[線程" + name + "],threadLocal移除當前線程" ); 71  MyThreadLocal.threadLocal.remove(); 72  } 73  } 74 try 75  { 76 Thread.sleep(1000); 77  } 78 catch (InterruptedException e) 79  { 80 e.printStackTrace(); 81 } 82 } 83 } 84 } 85 }

運行結果以下:

[線程Thread-0]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值!
i=0[線程IntegerTask1]當前線程不存在緩存,set 0 i=1[線程IntegerTask1]往threadLocal中set: 1 i=2[線程IntegerTask1]往threadLocal中set: 2 [線程Thread-1]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值! i=0[線程IntegerTask2]當前線程不存在緩存,set 0 i=3[線程IntegerTask1]往threadLocal中set: 3 i=3[線程IntegerTask1],threadLocal移除當前線程 i=1[線程IntegerTask2]往threadLocal中set: 1 [線程Thread-0]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值! i=4[線程IntegerTask1]當前線程不存在緩存,set 0 i=2[線程IntegerTask2]往threadLocal中set: 2 i=3[線程IntegerTask2]往threadLocal中set: 3 i=3[線程IntegerTask2],threadLocal移除當前線程 [線程Thread-1]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值! i=4[線程IntegerTask2]當前線程不存在緩存,set 0

結果驗證:

1.2個線程,2個threadLocal變量互不影響。

2.調用get方法時,對應ThreadLocalMap爲null會調用initialValue()方法,初始化threadLocal的值。

1.4 應用場景

ThreadLocal的實際應用場景:

1)數據結構用Map<String, Object>來避免建立多個ThreadLocal變量的麻煩。只需根據map的key就能夠獲取想要的value

private static final ThreadLocal<Map<String, Object>> loginContext = new ThreadLocal<>();

2)業務:線程級別,維護session,維護用戶登陸信息userID(登錄時插入,多個地方獲取)等,尤爲適合使用在WEB項目中(Tomcat容器,工做線程隔離)

2、變量可繼承的ThreadLocal==》InheritableThreadLocal

2.1 源碼註釋:

這個類擴展ThreadLocal,以提供從父線程到子線程的值的繼承:當建立子線程時,子線程會接收父元素所具備值的全部可繼承線程局部變量的初始值。正常狀況下,子線程的變量值與父線程的相同;然而,子線程可複寫childValue方法來自定義獲取父類變量。
當變量(例如,用戶ID、事務ID)中維護的每一個線程屬性必須自動傳輸到建立的任何子線程時,使用InheritableThreadLocal優於ThreadLocal。

2.2 源碼剖析

1.子線程啓動時,調用init方法,若是父線程有InheritableThreadLocal變量,則在子線程也生成一份

下圖是Thread類在init時執行的邏輯:

調用createInheritedMap方法,並調用childValue方法複製一份變量給子線程

 
 1 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
 2         return new ThreadLocalMap(parentMap);
 3     }
 4 
 5 private ThreadLocalMap(ThreadLocalMap parentMap) {
 6             Entry[] parentTable = parentMap.table;
 7             int len = parentTable.length;
 8             setThreshold(len);
 9             table = new Entry[len];
10 
11             for (int j = 0; j < len; j++) {
12                 Entry e = parentTable[j];
13                 if (e != null) {
14                     @SuppressWarnings("unchecked")
15                     ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
16                     if (key != null) {
17                         Object value = key.childValue(e.value);
18                         Entry c = new Entry(key, value);
19                         int h = key.threadLocalHashCode & (len - 1);
20                         while (table[h] != null)
21                             h = nextIndex(h, len);
22                         table[h] = c;
23                         size++;
24                     }
25                 }
26             }
27         }

2.支持用戶自定義childValue函數,用以子類獲取父類變量值的轉換:父類變量----childValue轉換函數-----》子類變量

InheritableThreadLocal默認childValue函數是直接返回:

protected T childValue(T parentValue) {
    return parentValue;
}

用戶可在建立InheritableThreadLocal變量時,覆蓋childValue函數,見3.3測試

2.3  功能測試

 1 package threadLocal;
 2 
 3 
 4 /**
 5  * 
 6  * @ClassName:MyInheritableThreadLocal
 7  * @Description:可繼承線程本地變量
 8  * @author denny.zhang
 9  * @date 2017年12月7日下午5:24:40
10  */
11 public class MyInheritableThreadLocal{
12     //線程本地共享變量
13     private static final InheritableThreadLocal<Object> threadLocal = new InheritableThreadLocal<Object>(){
14         /**
15          * ThreadLocal沒有被當前線程賦值時或當前線程剛調用remove方法後調用get方法,返回此方法值
16          */
17         @Override
18         protected Object initialValue()
19         {
20             System.out.println("[線程"+Thread.currentThread().getName()+"]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值!");
21             return null;
22         }
23         
24         @Override
25         protected Object childValue(Object parentValue) {
26             return (Integer)parentValue*2;
27         }
28         
29     };
30      
31     public static void main(String[] args){
32         //主線程設置1
33         threadLocal.set(1);
34         //1.開啓任務1線程
35         new Thread(new MyIntegerTask("IntegerTask1")).start();
36         //2.中間休息3秒,用以測試數據差別
37         try {
38             Thread.sleep(3000);
39         } catch (InterruptedException e) {
40             e.printStackTrace();
41         }
42         //開啓任務2線程
43         new Thread(new MyIntegerTask("IntegerTask2")).start();
44     }
45      
46     /**
47      * 
48      * @ClassName:MyIntegerTask
49      * @Description:整形遞增線程
50      * @author diandian.zhang
51      * @date 2017年12月4日上午10:00:41
52      */
53     public static class MyIntegerTask implements Runnable{
54         private String name;
55          
56         MyIntegerTask(String name)
57         {
58             this.name = name;
59         }
60  
61         @Override
62         public void run()
63         {
64             for(int i = 0; i < 5; i++)
65             {
66                 // ThreadLocal.get方法獲取線程變量
67                 if(null == MyInheritableThreadLocal.threadLocal.get())
68                 {
69                     // ThreadLocal.set方法設置線程變量
70                     MyInheritableThreadLocal.threadLocal.set(0);
71                     System.out.println("i="+i+"[線程" + name + "]當前線程不存在緩存,set 0");
72                 }
73                 else
74                 {
75                     int num = (Integer)MyInheritableThreadLocal.threadLocal.get();
76                     System.out.println("i="+i+"[線程" + name + "]get=" + num);
77                     MyInheritableThreadLocal.threadLocal.set(num + 1);
78                     System.out.println("i="+i+"[線程" + name + "]往threadLocal中set: " + MyInheritableThreadLocal.threadLocal.get());
79                     //當i=3即循環4次時,移除當前線程key
80                     if(i == 3)
81                     {
82                         System.out.println("i="+i+"[線程" + name + "],remove" );
83                         MyInheritableThreadLocal.threadLocal.remove();
84                     }
85                 }
86                 try
87                 {
88                     Thread.sleep(1000);
89                 }
90                 catch (InterruptedException e)
91                 {
92                     e.printStackTrace();
93                 }
94             }  
95         }
96     }
97 }

運行結果:

主線程變量值=1-----》主線程中變量值1
i=0[線程IntegerTask1]get=2-----》子線程1中變量值=2*1=2,驗證經過! i=0[線程IntegerTask1]往threadLocal中set: 3 i=1[線程IntegerTask1]get=3 i=1[線程IntegerTask1]往threadLocal中set: 4 i=2[線程IntegerTask1]get=4 i=2[線程IntegerTask1]往threadLocal中set: 5 i=0[線程IntegerTask2]get=2-----》主線程2中變量值=2*1=2,驗證經過! i=0[線程IntegerTask2]往threadLocal中set: 3 i=3[線程IntegerTask1]get=5 i=3[線程IntegerTask1]往threadLocal中set: 6 i=3[線程IntegerTask1],remove i=1[線程IntegerTask2]get=3 i=1[線程IntegerTask2]往threadLocal中set: 4 [線程Thread-0]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值! i=4[線程IntegerTask1]當前線程不存在緩存,set 0 i=2[線程IntegerTask2]get=4 i=2[線程IntegerTask2]往threadLocal中set: 5 i=3[線程IntegerTask2]get=5 i=3[線程IntegerTask2]往threadLocal中set: 6 i=3[線程IntegerTask2],remove [線程Thread-1]調用get方法時,當前線程共享變量沒值,調用initialValue獲取默認值! i=4[線程IntegerTask2]當前線程不存在緩存,set 0

如上圖,分析結果咱們可知,

1.子線程根據childValue函數獲取到了父線程的變量值。

2.多線程InheritableThreadLocal變量各自維護,無競爭關係。

2.4 應用場景

子線程變量數據依賴父線程變量,且自定義賦值函數。

例如:

開啓多線程執行任務時,總任務名稱叫mainTask 子任務名稱依次遞增mainTask-subTask一、mainTask-subTask二、mainTask-subTaskN等等

3、總結

本文分析了ThreadLocal原理、set(散列算法原理和測試驗證,再哈希擴容)、get、remove源碼,實際中的應用場景以及功能測試驗證。最後又分析了InheritableThreadLocal,使用該類子線程會繼承父線程變量,並自定義賦值函數。
讀完本文,相信你們對ThreadLocal一點也不擔憂了哈哈!

須要注意2點:

1.ThreadLocal不是用來解決線程安全問題的,多線程不共享,不存在競爭!目的是線程本地變量且只能單個線程內維護使用。

2.InheritableThreadLocal對比ThreadLocal惟一不一樣是子線程會繼承父線程變量,並自定義賦值函數。

3.項目若是使用了線程池,那麼當心線程回收後ThreadLocal、InheritableThreadLocal變量要remove,不然線程池回收後,變量還在內存中,後果不堪設想!(例如Tomcat容器的線程池,能夠在攔截器中處理:extends HandlerInterceptorAdapter,而後複寫afterCompletion方法,remove掉變量!!!)

 

=========參考=============

Why 0x61c88647?

相關文章
相關標籤/搜索