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


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

一、ThreadLocal

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

根據源碼,畫出原理圖如下:

注意點:

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

2.通過getMap()獲取每個子線程Thread持有自己的ThreadLocalMap實例, 因此它們是不存在並發競爭的。可以理解為每個線程有自己的變量副本。

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

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容器,工作線程隔離)

二、變量可繼承的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-subTask1、mainTask-subTask2、mainTask-subTaskN等等

三、總結

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

需要注意2點:

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

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

3.項目如果使用了線程池,那么小心線程回收后ThreadLocal、InheritableThreadLocal變量要remove,否則線程池回收后,變量還在內存中(key是弱引用被回收,但是value還在,內存泄漏),后果不堪設想!(例如Tomcat容器的線程池,可以在攔截器中處理:extends HandlerInterceptorAdapter,然后復寫afterCompletion方法,remove掉變量!!!)

 

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

Why 0x61c88647?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM