深入解析ThreadLocal源码
概念
ThreadLocal 是線程的局部變量, 是每一個線程所單獨持有的,其他線程不能對其進行訪問。
當使用ThreadLocal維護變量的時候 為每一個使用該變量的線程提供一個獨立的變量副本,即每個線程內部都會有一個該變量,這樣同時多個線程訪問該變量并不會彼此相互影響,因此他們使用的都是自己從內存中拷貝過來的變量的副本, 這樣就不存在線程安全問題,也不會影響程序的執行性能。
threadlocal,ThreadLocalMap,thread 三者關系圖
ThreadLocal的數據結構
Thread類中有個變量threadLocals,這個類型為ThreadLocal中的一個內部類ThreadLocalMap,這個類沒有實現map接口,就是一個普通的Java類,但是實現的類似map的功能。
ThreadLocal數據結構圖
每個線程都要自己的一個map,map是一個數組的數據結構存儲數據,每個元素是一個Entry,entry的key是threadlocal的引用,也就是當前變量的副本,value就是set的值。
ThreadLocal 源碼分析
1.set方法
//set 方法 public void set(T value) {//獲取當前線程Thread t = Thread.currentThread();//獲取到當前線程的 ThreadLocalMap 類型的變量 threadLocalsThreadLocalMap map = getMap(t);//如果存在則直接賦值if (map != null)map.set(this, value);else //如果不存在則給該線程創建 ThreadLocalMap 變量并賦值。createMap(t, value); }//獲取線程中的ThreadLocalMap 字段!! ThreadLocalMap getMap(Thread t) {return t.threadLocals; }//創建線程的變量 void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue); }總結:
對象實例與 ThreadLocal 變量的映射關系是存放的一個 Map 里面(這個 Map 是個抽象的 Map 并不是 java.util 中的 Map ),而這個 Map 是 Thread 類的一個字段!而真正存放映射關系的 Map 就是 ThreadLocalMap。
2.get方法
public T get() {//獲取當前ThreadThread t = Thread.currentThread();//獲取到當前線程的 ThreadLocalMap 類型的變量 threadLocalsThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//存在則返回值if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//不存在返回初始值return setInitialValue(); }//設置初始化值 private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value; }總結:
獲取當前線程的 ThreadLocalMap 變量,如果存在則返回值,不存在則創建并返回初始值。
ThreadLocalMap 源碼分析
ThreadLocal 的底層實現都是通過 ThreadLocalMap 來實現的
static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object). Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table. Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table; }table
存放對象實例與變量的關系,并且實例對象作為 key,變量作為 value 實現對應關系。這里的 key 采用的是對實例對象的弱引用,(因為我們這里的 key 是對象實例,每個對象實例有自己的生命周期,這里采用弱引用就可以在不影響對象實例生命周期的情況下對其引用)。
Entry
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;} }WeakReference 兩個構造函數
public class WeakReference<T> extends Reference<T> {public WeakReference(T referent) {super(referent);}public WeakReference(T referent, ReferenceQueue<? super T> q) {super(referent, q);}}1.WeakReference(T referent):referent就是被弱引用的對象(注意區分弱引用對象和被弱引用的對應,弱引用對象是指WeakReference的實例或者其子類的實例)
2.WeakReference(T referent, ReferenceQueue<? super T> q):與上面的構造方法比較,多了個ReferenceQueue,在對象被回收后,會把弱引用對象,也就是WeakReference對象或者其子類的對象,放入隊列ReferenceQueue中,注意不是被弱引用的對象,被弱引用的對象已經被回收了。
從上可知,這是個弱引用,意味這可能會被垃圾回收器回收掉,threadLocal.get()==null,也就意味著被回收掉了
1.set方法
private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;//獲取 hash 值,用于數組中的下標int i = key.threadLocalHashCode & (len-1);//如果數組該位置有對象則進入//通過hash尋找下標,尋找相等的ThreadLocal對象//1.找到相同的對象//2.一直往數組下一個下標查詢,指導下一個下標對應的為null,退出循環for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//k 相等則覆蓋舊值if (k == key) {e.value = value;return;}//此時說明此處 Entry 的 k 中的對象實例已經被回收了,需要替換掉這個位置的 key 和 valueif (k == null) {//key過期了,需要替換replaceStaleEntry(key, value, i);return;}}//沒找到//創建 Entry 對象tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)//擴容rehash(); }//獲取 Entry private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e); }//獲取從i到len的下一個增量 private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}//擴容 private void rehash() {expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresisif (size >= threshold - threshold / 4)resize();}//清除所有過時的對象 referent private void expungeStaleEntries() {Entry[] tab = table;int len = tab.length;for (int j = 0; j < len; j++) {Entry e = tab[j];if (e != null && e.get() == null)expungeStaleEntry(j);}}/*** 2倍擴容*/ private void resize() {Entry[] oldTab = table;int oldLen = oldTab.length;int newLen = oldLen * 2;Entry[] newTab = new Entry[newLen];int count = 0;for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null; // Help the GC} else {int h = k.threadLocalHashCode & (newLen - 1);while (newTab[h] != null)h = nextIndex(h, newLen);newTab[h] = e;count++;}}}setThreshold(newLen);size = count;table = newTab;}replaceStaleEntry
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;//從當前的staleSlot向前遍歷 i--;//為了把前面所有的已經被垃圾回收的也一起是放空間出來//這里只key被回收,value還沒被回收,entry更加沒回收,所依需要讓他們回收//同時也避免這樣存在很多過期的對象占用,導致這個時候剛好來了一個新元素達到閾值而觸發一次心的rehashint slotToExpunge = staleSlot;for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;// 這個時候是從數據下標小的往下標大的方向遍歷,i++剛好跟上面相反//這兩個遍歷為了在左邊遇到第一個空的entry到右邊遇到第一個空的entry之間查詢所有過期的對象//在右邊如果找到需要設置值的key 相同的時候開始清理然后返回,不再繼續遍歷for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();//說明之前已經存在相同的key,所依需要替換舊的值并且和前面那個過期的對象交換位置if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;//前面的第一個for循環(i--)往前查找的時候沒有找到的過期的,只有taleSlot//這個過期由于前面過期的對象已經通過交換位置的方式到index=i上了,所以需要清理i,而不是staleslotif (slotToExpunge == staleSlot)slotToExpunge = i;//清理過期數據cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}//如果在第一個for循環(i--)向前遍歷無任何過期對象//那么我們需要把slotToExpunge設置為后遍歷(i++)的第一個過期對象的位置//如果整個數組都沒找到要設置的key的時候,該key會設置在該staleslot的位置上//如果數組中存在要設置的key,那么上面也會通過交換位置的時候把有效值設置到staleSlot位置上//綜上: staleSlot存放的是有效值,不需要被清理if (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}// 如果key 在數組中不存在,則新建一個放進去tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// 如果有其他已經過期的對象,則清理此過期對象if (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }//獲取前一個序號 private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1); }expungeStaleEntry(int staleSlot)
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null;tab[i] = null;size--;} else {//采用開放地址法,刪除的元素是多個沖突元素重的一個,需要對后面的元素做處理(讓后面元素往前移動),這么做,住要是開放地址法尋找元素的時候,遇到null就停止熏著了,你前面key=null的時候已經設置entery為null,不移動后面的元素永遠訪問不了int h = k.threadLocalHashCode & (len - 1);//不相等說明hash是有沖突的if (h != i) {tab[i] = null;while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i; }ThreadLocal 應用場景
ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用(相同線程數據共享),也就是變量在線程間隔離(不同的線程數據隔離)而在方法或類間共享的場景。
總結
以上是生活随笔為你收集整理的深入解析ThreadLocal源码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 视频教程-嵌入式读图基础-智能硬件
- 下一篇: 【CE入门教程】使用Cheat Engi