這是我的第一篇博客,條理不是很清晰,不過還是希望能對大家有所幫助。
首先明確一下這個類的作用,ThreadLocal類是用來為每個線程提供了一份變量的副本,即每個線程的局部變量。每個線程都在自己的棧空間裡存有這個變量的值(對象的話就是引用),它們之間是線程隔離的,一個線程對這個ThreadLocal變量做的修改不會被其他線程看見。
下面先看一個例子。
1 import java.util.Random; 2 3 public class ThreadLocalTest { 4 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){ 5 protected Integer initialValue() { 6 return 6; 7 } 8 }; 9 10 public static int get(){ 11 return threadId.get(); 12 } 13 14 public static void main(String[] args) { 15 new Thread(new MThread("線程A")).start(); 16 new Thread(new MThread("線程B")).start(); 17 new Thread(new MThread("線程C")).start(); 18 } 19 20 static class MThread implements Runnable{ 21 private String name; 22 public MThread(String str){ 23 name = str; 24 } 25 public void run() { 26 int id = get(); 27 System.out.println("thread "+ name +"'s Id : "+ id); 28 threadId.set(10 * new Random().nextInt(10)); 29 for(int i = 0; i<3; i++){ 30 id = get(); 31 System.out.println("**thread "+ name +"'s Id : "+ id); 32 } 33 } 34 } 35 }
結果:
thread 線程B's Id : 6
**thread 線程B's Id : 60
**thread 線程B's Id : 60
**thread 線程B's Id : 60
thread 線程A's Id : 6
**thread 線程A's Id : 50
**thread 線程A's Id : 50
**thread 線程A's Id : 50
thread 線程C's Id : 6
**thread 線程C's Id : 90
**thread 線程C's Id : 90
**thread 線程C's Id : 90
從上面的結果中我們可以看出每個線程一開始調用get()得到的值都是6,而從輸出順序可知,線程A第一次調用get()時線程B已經在28行set()過threadId,但是A得到的還是6,說明B線程的改動對A是不可見的。
然後我們看看源碼中的注釋:
大意是說每個線程都有一份自己的,獨立於別人初始化的變量副本。下面就用源碼中的example來看看是不是獨立初始化的。
1 import java.util.concurrent.atomic.AtomicInteger; 2 3 public class ThreadLocalId { 4 private static final AtomicInteger nextId = new AtomicInteger(0); 5 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){ 6 protected Integer initialValue() { 7 return nextId.getAndIncrement(); 8 } 9 }; 10 11 public static int get(){ 12 return threadId.get(); 13 } 14 15 public static void main(String[] args) { 16 new Thread(new MThread("線程A")).start(); 17 new Thread(new MThread("線程B")).start(); 18 new Thread(new MThread("線程C")).start(); 19 } 20 21 static class MThread implements Runnable{ 22 private String name; 23 public MThread(String str){ 24 name = str; 25 } 26 public void run() { 27 int id ; 28 for(int i = 0; i<3; i++){ 29 id = get(); 30 System.out.println("thread "+ name +"'s Id : "+ id); 31 } 32 } 33 } 34 }
結果:
thread 線程A's Id : 1
thread 線程A's Id : 1
thread 線程C's Id : 2
thread 線程C's Id : 2
thread 線程B's Id : 0
thread 線程B's Id : 0
thread 線程B's Id : 0
thread 線程A's Id : 1
thread 線程C's Id : 2
從結果中可以看出每個線程的值都是不同的,這就是因為它們是獨立初始化的。因為它們第一次調用get()時如果之前沒有set()過值或remove()過,則會調用initialValue()方法來初始化值,而這個方法在第6行中進行了覆蓋,每次都是返回nextId的值,而nextId每次取完值都會自增,所以每個線程得到的值都會不同。上面的循環3次是為了說明源碼注釋中的這句:remains unchanged on subsequent calls,後續調用會返回不變的值(當然前提是期間沒有通過set()改變過值。。)。
這從get()的源碼中可以看出:
public T get() { Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
首先會得到當前調用get()方法的線程對象,然後通過getMap()方法獲取到與這個線程綁定的ThreadLocalMap對象,下面是getMap()的實現
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
很簡單,就是獲取Thread類的成員變量 ThreadLocal.ThreadLocalMap threadLocals = null; 在Thread類中默認為null,接上面的,當Map為空時,就調用setInitialValue();
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
而在setInitialValue()中,調用了覆蓋後的initialValue()取得一個值給value,剛剛是因為Map為null而調的這個方法,所以就會執行 createMap(t, value);而這個方法就是new一個ThreadLocalMap對象賦值給參數t線程,構造參數第一個this是Threadlocal對象,用來在Entry中對應著一個value。
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
可以看到ThreadLocalMap中持有一個Entry的數組,而每一個Entry裡就有一個ThreadLocal對應著一個value值,再結合get()的源碼就可以知道大體思路就是:threadLocal變量的值的獲取是首先找到當前線程對象,然後在取得這個線程對象的成員變量threadLocalMap,然後再通過threadLocalMap中的Entry[]取得這個當前ThreadLocal對應的Entry對象,然後Entry.value就得到了最終的值。至於怎麼找到當前Thread對應的Entry對象就是下面的代碼:
1 ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { 2 table = new Entry[INITIAL_CAPACITY]; 3 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 4 table[i] = new Entry(firstKey, firstValue); 5 size = 1; 6 setThreshold(INITIAL_CAPACITY); 7 } 8 9 private Entry getEntry(ThreadLocal key) { 10 int i = key.threadLocalHashCode & (table.length - 1); 11 Entry e = table[i]; 12 if (e != null && e.get() == key) 13 return e; 14 else 15 return getEntryAfterMiss(key, i, e); 16 }
在第4行放入的時候就是通過ThreadLocal的成員threadLocalHashCode按位與上Entry數組初始化容量-1獲得的下標,所以getEntry()時,第10行先得出下標i,再table[i]就能獲取得到Entry了。
最後用自己的話說就是:每個線程都有一個ThreaLocalMap變量,這個Map裡面有一個Entry數組,裡面保存了很多個ThreadLocal和Value的映射,當你調用get()時,發現線程沒有ThreadLocalMap就創建一個ThreadLocalMap,發現Entry數組裡沒有找到這ThreadLocal對應的Entry就創建一個Entry,在把值(這個值的獲取就是在那個initialValue裡面)放進去就好了,最後返回這個值。由此可知每個線程都是有用一塊空間來保存這個變量的,所以是線程隔離的,互不干擾的。
至於set()方法其實是類似的,看源碼就能知道了。
remove()方法提供主動刪除不再使用的threadLocal對應的Entry,這樣能避免出現內存洩漏。
在getEntry()中也有調用getEntryAfterMiss()來保障能清理掉stale(陳舊)的Entry,能在很大程度上解決內存洩漏問題。
這裡說只要線程還存活,都有持有一個隱式的引用到threadLocal變量,這個引用鏈為:thread->threadLocalMap->Entry->threadLocal,當線程結束後,其所有的線程局部實例的副本都會被GC回收(除非還存在其他的引用到這些副本)。
非常感謝你能看到最後,如果有什麼意見或建議,歡迎大家指出,我會積極改進的。