標籤:als oca 封裝 工作 它的 final ref 定義 turn
來聊一下ThreadLocal的實現原理和它的記憶體流失問題
首先來看一個官方樣本,這裡構造了一個ThreadId類,其作用是在每個線程中儲存各自的id,此id全域唯一,通過get可以擷取id。
1 private static class ThreadId { 2 // Atomic integer containing the next thread ID to be assigned 3 private static final AtomicInteger nextId = new AtomicInteger(1); 4 // Thread local variable containing each thread‘s ID 5 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { 6 @Override 7 protected Integer initialValue() { 8 return nextId.getAndIncrement(); 9 }10 };11 // Returns the current thread‘s unique ID, assigning it if necessary12 public static int get() {13 return threadId.get();14 }15 }
ThreadLocal的構造器是一個空函數,new一個ThreadLocal執行個體時,唯一的操作就是對threadLocalHashCode的初始化,很明顯這是一個hash值,猜測後續會用到map了。
1 private final int threadLocalHashCode = nextHashCode();
再來看調用get時,發生了什麼
/** * Returns the value in the current thread‘s copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread‘s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); //Thread中有一個ThreadLocalMap類型的執行個體欄位,獲得當前線程的threadLocalMap,其實就是返回t.threadLocals ThreadLocalMap map = getMap(t); if (map != null) { //以當前ThreadLocal執行個體為key,擷取entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") //得到entry中的value T result = (T)e.value; return result; } } //如果當前線程的threadLocalMap為null,或者當前threadLocal還未插入threadLocalMap,則進行相應初始化工作,無非就是初始化map、調用自訂的initialValue方法、將initialValue傳回值封裝成entry插入map return setInitialValue(); }
看一下map的構造
1 static class ThreadLocalMap { 2 ...... 3 static class Entry extends WeakReference<ThreadLocal<?>> { 4 /** The value associated with this ThreadLocal. */ 5 Object value; 6 7 Entry(ThreadLocal<?> k, Object v) { 8 //注意,這裡key被封裝成了一個WeakReference 9 super(k);10 value = v;11 }12 }13 ......14 private Entry[] table;15 ......16 private Entry getEntry(ThreadLocal<?> key) {17 //threadLocalHashCode在ThreadLocal初始化時就已產生,全域唯一。這裡18 int i = key.threadLocalHashCode & (table.length - 1);19 Entry e = table[i];20 if (e != null && e.get() == key)//e.get()即從WeakReference中取得threadLocal21 return e;22 else23 //有意思,一般的hash表都是使用一個鏈式結構來解決hash衝突,而這裡當hash衝突時進行線性探測24 return getEntryAfterMiss(key, i, e);25 }26 ......27 }
問題來了,為什麼entry中的key要封裝成WeakReference呢?
設想,當我們不再需要threadLocal了,以前例來說就是置ThreadId中類變數threadId為null(假設threadId不是final,也沒有被其他引用),而Thread類中的threadLocalMap中仍然持有threadId的引用,這就會產生記憶體流失。將threadLocal封裝成WeakReference作為key儲存,當threadId為null時,該threadLocal在gc時就會被回收,但此時value還在,ThreadLocal會在進行其他動作時刪除key為null的value。這確實存在一種記憶體流失隱患,如果之後不在進行ThreadLocal操作,就真釋放不掉value了。通常我們需要被聲明為ThreadLocal的變數,在運行期間都是不期望它被回收的,所以我們通常會將其聲明為static final,如果有回收的需求,也請使用ThreadLocal的remove進行顯示釋放。
java基礎-ThreadLocal