這是我的第一篇博客,條理不是很清晰,不過還是希望能對大家有所幫助。
首先明確一下這個類的作用,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回收(除非還存在其他的引用到這些副本)。
非常感謝你能看到最後,如果有什麼意見或建議,歡迎大家指出,我會積極改進的。