摘要:雖然類名中帶有字樣,但是實(shí)際上并不是接口的子類。是弱連接接口,這意味著如果僅有指向某一類,其任然有可能被回收掉。這里使用弱連接的意義,是為了防止業(yè)務(wù)代碼中置空對(duì)象,但是由于存在連接可達(dá),所以仍然無法回收掉該對(duì)象的情況發(fā)生。
零 前期準(zhǔn)備 0 FBI WARNING
文章異常啰嗦且繞彎。
1 版本JDK 版本 : OpenJDK 11.0.1
IDE : idea 2018.3
2 ThreadLocal 簡介ThreadLocal 是 java 多線程中經(jīng)常使用到的緩存工具,被封裝在 java.lang 包下。
3 Demoimport io.netty.util.concurrent.FastThreadLocal; public class ThreadLocalDemo { public static void main(String[] args) { //jdk 的 ThreadLocal ThreadLocaltl = new ThreadLocal<>(); long tlBeginTime = System.nanoTime(); //set(...) 方法存入元素 tl.set("test"); //get() 方法獲取元素 String get = tl.get(); System.out.println("tl before remove: " + get); //remove() 方法刪除元素 tl.remove(); get = tl.get(); System.out.println("tl after remove: " + get); System.out.println(System.nanoTime() - tlBeginTime); //以下代碼為著名 io 框架 Netty 的 FastThreadLocal 類的使用 //FastThreadLocal,基本的使用方法和 ThreadLocal 沒有區(qū)別 //FastThreadLocal 的實(shí)例對(duì)象創(chuàng)建比較慢,但是元素的獲取、增、刪的性能很好 FastThreadLocal fastTl = new FastThreadLocal<>(); long fastTlBeginTime = System.nanoTime(); fastTl.set("test"); String fastGet = fastTl.get(); System.out.println("tl2 before remove: " + fastGet); fastTl.remove(); fastGet = fastTl.get(); System.out.println("tl2 after remove: " + fastGet); System.out.println(System.nanoTime() - fastTlBeginTime); //此處的 Netty 使用 4.1.33.Final 的版本 //筆者跑了一下,F(xiàn)astThreadLocal 的增刪查操作大概比 ThreadLocal 快十倍 //但是此處僅為簡陋測(cè)試,并不嚴(yán)謹(jǐn) } }
FastThreadLocal 的源碼暫不展開,將來有機(jī)會(huì)多帶帶開一章去學(xué)習(xí)。這里先理解 ThreadLocal。
一 ThreadLocalMap在了解 ThreadLocal 的全貌之前先來理解一下 ThreadLocalMap 類。
其為 ThreadLocal 的靜態(tài)內(nèi)部類。雖然類名中帶有 map 字樣,但是實(shí)際上并不是 Map 接口的子類。
ThreadLocalMap 本質(zhì)上是數(shù)組。每個(gè) Thread 實(shí)例對(duì)象都會(huì)維護(hù)多個(gè) ThreadLocalMap 對(duì)象:
ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
但是需要注意的是,在默認(rèn)情況下,線程對(duì)象的 ThreadLocalMap 對(duì)象們都是未初始化的,需要使用 createMap(...) 方法去初始化:
//ThreadLocal.class void createMap(Thread t, T firstValue) { //此處 ThreadLocal 將自身作為 key 值存入了 map 中 t.threadLocals = new ThreadLocalMap(this, firstValue); }
可以想到的是,此處是為了提高線程的性能,而設(shè)計(jì)了一個(gè)懶加載(Lazy)的調(diào)用模式。
[但是實(shí)際上這是理想情況,對(duì)于主線程來說,Collections、StringCoding 等的工具類在 jdk 加載時(shí)期就會(huì)調(diào)用 ThreadLocal,所以 ThreadLocalMap 肯定會(huì)被創(chuàng)建好]
再來看一下 ThreadLocalMap 的構(gòu)造方法:
//ThreadLocalMap.class ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) { //Entry 是 ThreadLocalMap 的靜態(tài)內(nèi)部類,代表節(jié)點(diǎn)的對(duì)象 //table 是一個(gè) Entry 數(shù)組,代表鏈表 table = new Entry[INITIAL_CAPACITY]; //這里調(diào)用 key 的 hash 值進(jìn)行數(shù)組下標(biāo)計(jì)算 //INITIAL_CAPACITY 為常量 16 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; //threshold = INITIAL_CAPACITY * 2 / 3 setThreshold(INITIAL_CAPACITY); }Entry
Entry 是 ThreadLocalMap 的靜態(tài)內(nèi)部類,本質(zhì)上是數(shù)組的節(jié)點(diǎn) value 的封裝:
static class Entry extends WeakReference> { //儲(chǔ)存的 value 值 Object value; Entry(ThreadLocal> k, Object v) { //調(diào)用父類的方法,會(huì)將 ThreadLocal 存入 Reference 中的 referent 對(duì)象中 super(k); value = v; } }
由上可知 Entry 繼承了 WeakReference。WeakReference 是弱連接接口,這意味著如果僅有 Entry 指向某一 ThreadLocal 類,其任然有可能被 GC 回收掉。
這里使用弱連接的意義,是為了防止業(yè)務(wù)代碼中置空 ThreadLocal 對(duì)象,但是由于存在連接可達(dá),所以仍然無法回收掉該對(duì)象的情況發(fā)生。 即可以這么說,如果使用者在業(yè)務(wù)代碼中存在可達(dá)的強(qiáng)連接引用對(duì)象,那么 ThreadLocal 永遠(yuǎn)不會(huì)被 GC 清理掉;但是如果強(qiáng)連接消失了,那么弱連接并不能保證它一定存活。當(dāng)然換句話說,強(qiáng)連接消失的時(shí)候,證明使用者已經(jīng)不需要這個(gè)對(duì)象了,那么它被消滅也是應(yīng)該的。二 存入元素
來看一下 ThreadLocal 的 set(...) 方法:
//step 1 //ThreadLocal.class public void set(T value) { //獲取當(dāng)前線程的實(shí)例對(duì)象 Thread t = Thread.currentThread(); //通過實(shí)例對(duì)象獲取到 map //map 實(shí)際上是定義在 Thread 類中的 ThreadLocalMap 類型的對(duì)象 ThreadLocalMap map = getMap(t); if (map != null) { //存入元素 map.set(this, value); } else { //如果 map 不存在,會(huì)在這里創(chuàng)建 map createMap(t, value); } } //step 2 //ThreadLocalMap.class private void set(ThreadLocal> key, Object value) { //獲取數(shù)組 table Entry[] tab = table; //獲取長度 int len = tab.length; //根據(jù) hash 值算出下標(biāo) int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //nextIndex(...) 方法獲取數(shù)組的下一個(gè)下標(biāo)的元素 //基本等同于 i + 1,但是一般情況下不需要用到 //從節(jié)點(diǎn)中獲取 ThreadLocal 對(duì)象 ThreadLocal> k = e.get(); //正常情況下 k == key,第一次存值的時(shí)候 value = null if (k == key) { e.value = value; return; } //正常情況下不會(huì)出現(xiàn) if (k == null) { replaceStaleEntry(key, value, i); return; } } //進(jìn)入此處語句的條件是 k 并不為 null,且 key 不等于數(shù)組內(nèi)現(xiàn)存的所有 ThreadLocal //則在此處符合要求的下標(biāo)處新建一個(gè)節(jié)點(diǎn),并添加到 table 數(shù)組中 //注意,這里其實(shí)是覆蓋操作,會(huì)覆蓋掉之前在此下標(biāo)處的節(jié)點(diǎn) tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }三 獲取元素
來看一下 ThreadLocal 的 get() 方法:
//step 1 //ThreadLocal.class public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //map 為 null 的情況下會(huì)進(jìn)入該方法 //此處會(huì)將 null 作為 value,當(dāng)前 ThreadLocal 作為 key,傳入 ThreadLocalMap 中 return setInitialValue(); } //step 2 //ThreadLocalMap.class private Entry getEntry(ThreadLocal> key) { //算出下標(biāo)值 int i = key.threadLocalHashCode & (table.length - 1); //獲取節(jié)點(diǎn) Entry e = table[i]; if (e != null && e.get() == key) return e; else //此處會(huì)輪詢整個(gè)數(shù)組去尋找,實(shí)在找不到會(huì)返回 null return getEntryAfterMiss(key, i, e); }
基本邏輯和 set(...) 方法差不多,不多贅述。
四 移除元素來看一下 ThreadLocal 的 remove() 方法:
//step 1 //ThreadLocalMap.class public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { //調(diào)用 ThreadLocalMap 的 remove(...) 方法 m.remove(this); } } //step 2 //ThreadLocalMap.class private void remove(ThreadLocal> key) { Entry[] tab = table; int len = tab.length; //算出下標(biāo) int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { //此處是一個(gè)和 set(...) 中很像的輪詢方法 //比對(duì) key 值,如果相等的話會(huì)調(diào)用 clear() 方法清理掉 if (e.get() == key) { e.clear(); //此方法用于清理 key 值為 null 的節(jié)點(diǎn) expungeStaleEntry(i); return; } } } //step 3 //Reference.class public void clear() { //Reference 是 WeakReference 的父類,即也就是 Entry 的父類 //將值置空 this.referent = null; }五 ThreadLocal 的 hash 值
上述方法多次使用到了用 hash 去計(jì)算數(shù)組下標(biāo)的操作。如果不同 ThreadLocal 的 hash 值相同,那么就會(huì)造成計(jì)算出來的下標(biāo)相同,會(huì)相互影響存入的值。
所以 ThreadLocal 的 hash 值一定不能相同。
在 ThreadLocal 中,hash 值是一個(gè) int 類型的變量:
private final int threadLocalHashCode = nextHashCode();
其調(diào)用了靜態(tài)方法 nextHashCode() 去產(chǎn)生 hash 值:
//ThreadLocal.class private static int nextHashCode() { //HASH_INCREMENT = 0x61c88647 (一個(gè)很神奇的用來解決 hash 沖突的數(shù)字) //nextHashCode 是一個(gè)定義在 ThreadLocal 中的靜態(tài) AtomicInteger 類型變量 //getAndAdd(...) 方法會(huì)每次給 nextHashCode 的值加上 HASH_INCREMENT 的值,并返回最終的相加結(jié)果值 return nextHashCode.getAndAdd(HASH_INCREMENT); }
jdk9 以后官方應(yīng)該比較希望使用 VarHandler 類來取代 Atomic 類,所以在不久的未來,很可能相關(guān)方法會(huì)有一些變動(dòng)。
六 一點(diǎn)嘮叨ThreadLocal 的源代碼還是比較簡潔的,方法封裝不多,讀起來不算費(fèi)勁,有一些算法層面的東西比較麻煩,但是不影響閱讀。
Netty 的 FastThreadLocal,其設(shè)計(jì)就要比 ThreadLocal 復(fù)雜得多,有機(jī)會(huì)再深入學(xué)習(xí)。
本文僅為個(gè)人的學(xué)習(xí)筆記,可能存在錯(cuò)誤或者表述不清的地方,有緣補(bǔ)充
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/73304.html
摘要:零前期準(zhǔn)備文章異常啰嗦且繞彎。二是底層真正起作用的類,并且提供了大量的靜態(tài)方法。在普通的線程中,這個(gè)對(duì)象由于本身沒有的原生支持,所以只能附著在對(duì)象當(dāng)中。同一個(gè)線程中如果創(chuàng)建多個(gè)對(duì)象,獲取到的是同一個(gè)。 零 前期準(zhǔn)備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 Netty 版本 : net...
摘要:零前期準(zhǔn)備文章異常啰嗦且繞彎。版本版本簡介是中默認(rèn)的實(shí)現(xiàn)類,常與結(jié)合進(jìn)行多線程并發(fā)操作。所以方法的主體其實(shí)就是去喚醒被阻塞的線程。本文僅為個(gè)人的學(xué)習(xí)筆記,可能存在錯(cuò)誤或者表述不清的地方,有緣補(bǔ)充 零 前期準(zhǔn)備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 ThreadLocal 簡介 ...
摘要:接上篇三和在進(jìn)行的序列化和反序列化源碼解析之前先了解一下其主體工具類。是中用于序列化的主體。同時(shí)為了兼顧性能做了很多有意思的設(shè)計(jì),比如獲取適配器的時(shí)候的雙緩存設(shè)計(jì),應(yīng)該是為了提高解析器的復(fù)用效率,具體有待研究。 接上篇 三 JsonReader 和 JsonWriter 在進(jìn)行 json 的序列化和反序列化源碼解析之前先了解一下其主體工具類。 1 JsonReader JsonRead...
摘要:變量的說法來自于,這是在多線程模型下出現(xiàn)并發(fā)問題的一種解決方案。目前已經(jīng)有庫實(shí)現(xiàn)了應(yīng)用層棧幀的可控編碼,同時(shí)可以在該棧幀存活階段綁定相關(guān)數(shù)據(jù),我們便可以利用這種特性實(shí)現(xiàn)類似多線程下的變量。 ThreadLocal變量的說法來自于Java,這是在多線程模型下出現(xiàn)并發(fā)問題的一種解決方案。ThreadLocal變量作為線程內(nèi)的局部變量,在多線程下可以保持獨(dú)立,它存在于線程的生命周期內(nèi),可以在...
閱讀 668·2023-04-26 00:33
閱讀 3689·2021-11-24 09:39
閱讀 3329·2021-09-22 15:34
閱讀 2517·2019-08-23 18:07
閱讀 3061·2019-08-23 18:04
閱讀 3929·2019-08-23 16:06
閱讀 3045·2019-08-23 15:27
閱讀 1742·2019-08-23 14:32