一.ThreadLocal介紹html
1.1 ThreadLocal的功能java
1.2 ThreadLocal使用示例算法
二.源碼分析-ThreadLocalapi
2.1 ThreadLocal的類層級關係數組
2.2 ThreadLocal的屬性字段多線程
2.3 建立ThreadLocal對象併發
2.4 ThreadLocal-set操做ide
2.5 ThreadLocal-get操做源碼分析
2.6 ThreadLocal-remove操做post
3.0 線性探測算法解決hash衝突
3.1 Entry內部類
3.5 清理陳舊Entry和rehash
四.總結
咱們知道,變量從做用域範圍進行分類,能夠分爲「全局變量」、「局部變量」兩種:
1.全局變量(global variable),好比類的靜態屬性(加static關鍵字),在類的整個生命週期都有效;
2.局部變量(local variable),好比在一個方法中定義的變量,做用域只是在當前方法內,方法執行完畢後,變量就銷燬(釋放)了;
使用全局變量,當多個線程同時修改靜態屬性,就容易出現併發問題,致使髒數據;而局部變量通常來講不會出現併發問題(在方法中開啓多線程併發修改局部變量,仍可能引發併發問題);
再看ThreadLocal,能夠用來保存局部變量,只不過這個「局部」是指「線程」做用域,也就是說,該變量在該線程的整個生命週期中有效。
關於ThreadLocal的使用場景,能夠查看ThreadLocal的使用場景分析。
ThreadLocal使用很是簡單。
package cn.ganlixin; import org.junit.Test; import java.util.Arrays; import java.util.List; public class TestThreadLocal { private static class Goods { public Integer id; public List<String> tags; } @Test public void testReference() { Goods goods1 = new Goods(); goods1.id = 10; goods1.tags = Arrays.asList("healthy", "cheap"); ThreadLocal<Goods> threadLocal = new ThreadLocal<>(); threadLocal.set(goods1); Goods goods2 = threadLocal.get(); System.out.println(goods1); // cn.ganlixin.TestThreadLocal$Goods@1c655221 System.out.println(goods2); // cn.ganlixin.TestThreadLocal$Goods@1c655221 goods2.id = 100; System.out.println(goods1.id); // 100 System.out.println(goods2.id); // 100 threadLocal.remove(); System.out.println(threadLocal.get()); // null } @Test public void test2() { // 一個線程中,能夠建立多個ThreadLocal對象,多個ThreadLoca對象互不影響 ThreadLocal<String> threadLocal1 = new ThreadLocal<>(); ThreadLocal<String> threadLocal2 = new ThreadLocal<>(); // ThreadLocal存的值默認爲null System.out.println(threadLocal1.get()); // null threadLocal1.set("this is value1"); threadLocal2.set("this is value2"); System.out.println(threadLocal1.get()); // this is value1 System.out.println(threadLocal2.get()); // this is value2 // 能夠重寫initialValue進行設置初始值 ThreadLocal<String> threadLocal3 = new ThreadLocal<String>() { @Override protected String initialValue() { return "this is initial value"; } }; System.out.println(threadLocal3.get()); // this is initial value } }
ThreadLocal類中有一個內部類ThreadLocalMap,這個類特別重要,ThreadLocal的各類操做基本都是圍繞ThreadLocalMap進行的。
對於ThreadLocalMap有來講,它內部定義了一個Entry內部類,有一個table屬性,是一個Entry數組,他們有一些類似的地方,可是ThreadLocalMap和HashMap並無什麼關係。
先大概看一下內存關係圖,不理解也不要緊,看了後面的代碼應該就能理解了:
大概解釋一下,棧中的Thread ref(引用)堆中的Thread對象,Thread對象有一個屬性threadlocals(ThreadLocalMap類型),這個Map中每一項(Entry)的value是ThreadLocal.set()的值,而Map的key則是ThreadLocal對象。
下面在介紹源碼的時候,會從兩部分進行介紹,先介紹ThreadLocal的經常使用api,而後再介紹ThreadLocalMap,由於ThreadLocal的api內部其實都是在操做ThreadLocalMap,因此看源碼時必定要知道他們倆之間的關係。
ThreadLocal有3個屬性,主要的功能就是生成ThreadLocal的hash值。
// threadLocalHashCode用來表示當前ThreadLocal對象的hashCode,經過計算得到 private final int threadLocalHashCode = nextHashCode(); // 一個AtomicInteger類型的屬性,功能就是計數,各類操做都是原子性的,在併發時不會出現問題 private static AtomicInteger nextHashCode = new AtomicInteger(); // hash值的增量,不是隨便指定的,被稱爲「黃金分割數」,能讓hash結果均衡分佈 private static final int HASH_INCREMENT = 0x61c88647; /** * 經過計算,爲當前ThreadLocal對象生成一個HashCode */ private static int nextHashCode() { // 獲取當前nextHashCode,而後遞增HASH_INCREMENT return nextHashCode.getAndAdd(HASH_INCREMENT); }
ThreadLocal類,只有一個無參構造器,若是須要是指默認值,則能夠重寫initialValue方法:
public ThreadLocal() {} /** * 初始值默認爲null,要設置初始值,只須要設置爲方法返回值便可 * * @return ThreadLocal的初始值 */ protected T initialValue() { return null; }
須要注意的是initialValue方法並不會在建立ThreadLocal對象的時候設置初始值,而是延遲執行:當ThreadLocal直接調用get時纔會觸發initialValue執行(get以前沒有調用set來設置過值),initialValue方法在後面還會介紹。
下面這段代碼只給出了ThreadLocal的set代碼:
public void set(T value) { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取當前線程的ThreadLocalMap屬性,ThreadLocal有一個threadLocals屬性(ThreadLocalMap類型) ThreadLocalMap map = getMap(t); if (map != null) { // 若是當前線程有關聯的ThreadLocalMap對象,則調用ThreadLocalMap的set方法進行設置 map.set(this, value); } else { // 建立一個與當前線程關聯的ThreadLocalMap對象,並設置對應的value createMap(t, value); } } /** * 獲取線程關聯的ThreadLocalMap對象 */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * 建立ThreadLocalMap * @param t key爲當前線程 * @param firstValue value爲ThreadLocal.set的值 */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
若是想當即瞭解ThreadLocalMap的set方法,則可點此跳轉!
前面說過「重寫ThreadLocal的initialValue方法來設置ThreadLocal的默認值,並非在建立ThreadLocal的時候執行的,而是在直接get的時候執行的」,看了下面的代碼,就知道這句話的具體含義了,感受設計很巧妙:
public T get() { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取當前線程對象的threadLocals屬性 ThreadLocalMap map = getMap(t); // 若當前線程對象的threadLocals屬性不爲空(map不爲空) if (map != null) { // 當前ThreadLocal對象做爲key,獲取ThreadLocalMap中對應的Entry ThreadLocalMap.Entry e = map.getEntry(this); // 若是找到對應的Entry,則證實該線程的該ThreadLocal有值,返回值便可 if (e != null) { @SuppressWarnings("unchecked") T result = (T) e.value; return result; } } // 1.當前線程對象的threadLocals屬性爲空(map爲空) // 2.或者map不爲空,可是未在map中查詢到以該ThreadLocal對象爲key對應的entry // 這兩種狀況,都會進行設置初始值,並將初始值返回 return setInitialValue(); } /** * 設置ThreadLocal初始值 * * @return 初始值 */ private T setInitialValue() { // 調用initialValue方法,該方法能夠在建立ThreadLocal的時候重寫 T value = initialValue(); Thread t = Thread.currentThread(); // 獲取當前線程的threadLocals屬性(map) ThreadLocalMap map = getMap(t); if (map != null) { // threadLocals屬性值不爲空,則進行調用ThreadLocalMap的set方法 map.set(this, value); } else { // 沒有關聯的threadLocals,則建立ThreadLocalMap,並在map中新增一個Entry createMap(t, value); } // 返回初始值 return value; } /** * 初始值默認爲null,要設置初始值,只須要設置爲方法返回值便可 * 建立ThreadLocal設置默認值,能夠覆蓋initialValue方法,initialValue方法不是在建立ThreadLocal時執行,而是這個時候執行 * * @return ThreadLocal的初始值 */ protected T initialValue() { return null; }
通常是在ThreadLocal對象使用完後,調用ThreadLocal的remove方法,在必定程度上,能夠避免內存泄露;
/** * 刪除當前線程中threadLocals屬性(map)中的Entry(以當前ThreadLocal爲key的) */ public void remove() { // 獲取當前線程的threadLocals屬性(ThreadLocalMap) ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { // 調用ThreadLocalMap的remove方法,刪除map中以當前ThreadLocal爲key的entry m.remove(this); } }
在介紹ThreadLocalMap的以前,強烈建議先了解一下線性探測算法,這是一種解決Hash衝突的方案,若是不瞭解這個算法就去看ThreadLocalMap的源碼就會很是吃力,會感到莫名其妙。
連接在此:利用線性探測法解決hash衝突
ThreadLocalMap是ThreadLocal的內部類,ThreadLocalMap底層使用數組實現,每個數組的元素都是Entry類型(在ThreadLocalMap中定義的),源碼以下:
/** * ThreadLocalMap中存放的元素類型,繼承了弱引用類 */ static class Entry extends WeakReference<ThreadLocal<?>> { // key對應的value,注意key是ThreadLocal類型 Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap和HashMap相似,比較一下:
a:底層都是使用數組實現,數組元素類型都是內部定義,Java8中,HashMap的元素是Node類型(或者TreeNode類型),ThreadLocalMap中的元素類型是Entry類型;
b.都是經過計算獲得一個值,將這個值與數組的長度(容量)進行與操做,肯定Entry應該放到哪一個位置;
c.都有初始容量、負載因子,超過擴容閾值將會觸發擴容;可是HashMap的初始容量、負載因子是能夠更改的,而ThreadLocalMap的初始容量和負載因子不可修改;
注意Entry繼承自WeakReference類,在實例化Entry時,將接收的key傳給父類構造器(也就是WeakReference的構造器),WeakReference構造器又將key傳給它的父類構造器(Reference):
// 建立Reference對象,接受一個引用 Reference(T referent) { this(referent, null); } // 設置引用 Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
關於Java的各類引用,能夠參考:Java-強引用、軟引用、弱引用、虛引用
// ThreadLocalMap的初始容量 private static final int INITIAL_CAPACITY = 16; // ThreadLocalMap底層存數據的數組 private Entry[] table; // ThreadLocalMap中元素的個數 private int size = 0; // 擴容閾值,當size達到閾值時會觸發擴容(loadFactor=2/3;newCapacity=2*oldCapacity) private int threshold; // Default to 0
建立ThreadLocalMap,是在第一次調用ThreadLocal的set或者get方法時執行,其中第一次未set值,直接調用get時,就會利用ThreadLocal的初始值來建立ThreadLocalMap。
ThreadLocalMap內部類的源碼以下:
/** * 初始化一個ThreadLocalMap對象(第一次調用ThreadLocal的set方法時建立),傳入ThreadLocal對象和對應的value */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 建立一個Entry數組,容量爲16(默認) table = new Entry[INITIAL_CAPACITY]; // 計算新增的元素,應該放到數組的哪一個位置,根據ThreadLocal的hash值與初始容量進行"與"操做 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 建立一個Entry,設置key和value,注意Entry中沒有key屬性,key屬性是傳給Entry的父類WeakReference table[i] = new Entry(firstKey, firstValue); // 初始容量爲1 size = 1; // 設置擴容閾值 setThreshold(INITIAL_CAPACITY); } /** * 設置擴容閾值,接收容量值,負載因子固定爲2/3 */ private void setThreshold(int len) { threshold = len * 2 / 3; }
ThreadLocal的set方法,其實核心就是調用ThreadLocalMap的set方法,set方法的流程比較長
/** * 爲當前ThreadLocal對象設置value */ private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; // 計算新元素應該放到哪一個位置(這個位置不必定是最終存放的位置,由於可能會出現hash衝突) int i = key.threadLocalHashCode & (len - 1); // 判斷計算出來的位置是否被佔用,若是被佔用,則須要找出應該存放的位置 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { // 獲取Entry中key,也就是弱引用的對象 ThreadLocal<?> k = e.get(); // 判斷key是否相等(判斷弱引用的是否爲同一個ThreadLocal對象)若是是,則進行覆蓋 if (k == key) { e.value = value; return; } // k爲null,也就是Entry的key已經被回收了,當前的Entry是一個陳舊的元素(stale entry) if (k == null) { // 用新元素替換掉陳舊元素,同時也會清理其餘陳舊元素,防止內存泄露 replaceStaleEntry(key, value, i); return; } } // map中沒有ThreadLocal對應的key,或者說沒有找到陳舊的Entry,則建立一個新的Entry,放入數組中 tab[i] = new Entry(key, value); // ThreadLocalMap的元素數量加1 int sz = ++size; // 先清理map中key爲null的Entry元素,該Entry也應該被回收掉,防止內存泄露 // 若是清理出陳舊的Entry,那麼就判斷是否須要擴容,若是須要的話,則進行rehash if (!cleanSomeSlots(i, sz) && sz >= threshold) { rehash(); } }
上面最後幾行代碼涉及到清理陳舊Entry和rehash,這兩塊的代碼在下面。
陳舊的Entry,是指Entry的key爲null,這種狀況下,該Entry是不可訪問的,可是卻不會被回收,爲了不出現內存泄漏,因此須要在每次get、set、replace時,進行清理陳舊的Entry,下面只給出一部分代碼:
/** * 清理map中key爲null的Entry元素,該Entry也應該被回收掉,防止內存泄露 * * @param i 新Entry插入的位置 * @param n 數組中元素的數量 * @return 是否有陳舊的entry的清除 */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ((n >>>= 1) != 0); return removed; } private void rehash() { // 清除底層數組中全部陳舊的(stale)的Entry,也就是key爲null的Entry // 同時每清除一個Entry,就對其後面的Entry從新計算hash,獲取新位置,使用線性探測法,從新肯定最終位置 expungeStaleEntries(); // 清理完陳舊Entry後,判斷是否須要擴容 if (size >= threshold - threshold / 4) { // 擴容時,容量變爲舊容量的2倍,再進行rehash,並使用線性探測發肯定Entry的新位置 resize(); } }
在rehash的時候,涉及到「線性探測法」,是一種用來解決hash衝突的方案,能夠查看利用線性探測法解決hash衝突瞭解詳情。
remove操做,是調用ThreadLocal.remove()方法時,刪除當前線程的ThreadLocalMap中該ThreadLocal爲key的Entry。
/** * 移除當前線程的threadLocals屬性中key爲ThreadLocal的Entry * * @param key 要移除的Entry的key(ThreadLocal對象) */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; // 計算出該ThreadLocal對應的key應該存放的位置 int i = key.threadLocalHashCode & (len - 1); // 找到指定位置,開始按照線性探測算法進行查找到該Thread的Entry for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { // 若是Entry的key相同 if (e.get() == key) { // 調用WeakReference的clear方法,Entry的key是弱引用,指向ThreadLocal,如今將key指向null // 則該ThreadLocal對象在會在下一次gc時,被垃圾收集器回收 e.clear(); // 將該位置的Entry中的value置爲null,因而value引用的對象也會被垃圾收集器回收(不會形成內存泄漏) // 同時內部會調整Entry的順序(開放探測算法的特色,刪除元素後會從新調整順序) expungeStaleEntry(i); return; } } }
在學習ThreadLocal類源碼的過程仍是受益頗多的:
1.ThreadLocal的使用場景;
2.initialValue的延遲執行;
3.HashMap使用鏈表+紅黑樹解決hash衝突,ThreadLocalMap使用線性探測算法(開放尋址)解決hash衝突
另外,ThreadLocal還有一部份內容,是關於弱引用和內存泄漏的問題,我會繼續寫一篇博客進行總結。