溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java ThreadLocal的源碼知識(shí)有哪些

發(fā)布時(shí)間:2021-11-19 16:49:10 來(lái)源:億速云 閱讀:171 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容介紹了“java ThreadLocal的源碼知識(shí)有哪些”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

簡(jiǎn)介

ThreadLocal是每個(gè)線程自己維護(hù)的一個(gè)存儲(chǔ)對(duì)象的數(shù)據(jù)結(jié)構(gòu),線程間互不影響實(shí)現(xiàn)線程封閉。一般我們通過(guò)ThreadLocal對(duì)象的get/set方法存取對(duì)象。

源碼分析

ThreadLocal的set方法源碼如下

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t); // 根據(jù)當(dāng)前線程獲得ThreadLocalMap對(duì)象if (map != null)map.set(this, value); // 如果有則setelsecreateMap(t, value); // 否則創(chuàng)建ThreadLocalMap對(duì)象}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

通過(guò)getMap方法,可見(jiàn)我們返回的map實(shí)際上是Thread對(duì)象的threadLocals屬性。而這個(gè)ThreadLocalMap就是用來(lái)存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu)。

ThreadLocalMap介紹

ThreadLocalMap是ThreadLocal的核心,定義在ThreadLocal類里的內(nèi)部類,他維護(hù)了一個(gè)Enrty數(shù)組。ThreadLocal存/取數(shù)據(jù)都是通過(guò)操作Enrty數(shù)組來(lái)實(shí)現(xiàn)的。

Enrty數(shù)組作為一個(gè)哈希表,將對(duì)象通過(guò)開放地址方法散列到這個(gè)數(shù)組中。作為對(duì)比,HashMap則是通過(guò)鏈表法將對(duì)象散列到數(shù)組中。

開放地址法就是元素散列到數(shù)組中的位置如果有沖突,再以某種規(guī)則在數(shù)組中找到下一個(gè)可以散列的位置,而在ThreadLocalMap中則是使用線性探測(cè)的方式向后依次查找可以散列的位置。

Enery介紹

Enery在這里我們稱之為元素,是散列表中維護(hù)的對(duì)象單元。

// 哈希映射表中的元素使用其引用字段作為鍵(它始終是ThreadLocal對(duì)象)繼承WeakReference。// 注意,null鍵(即entry.get()== null)表示不再引用該鍵,因此可以從表中刪除該元素。// 這些元素在下面的代碼中稱為“舊元素”。// 這些“舊元素”就是臟對(duì)象,因?yàn)榇嬖谝貌粫?huì)被GC,// 為避免內(nèi)存泄露需要代碼里清理,將引用置為null,那么這些對(duì)象之后就會(huì)被GC清理。// 實(shí)際上后面的代碼很大程度上都是在描述如何清理“舊元素”的引用static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}

到這里可能有兩個(gè)疑問(wèn)

1、既然要存儲(chǔ)的內(nèi)容是線程獨(dú)有的對(duì)象,為什么不直接在Thread里設(shè)置一個(gè)屬性直接存儲(chǔ)該對(duì)象?或者說(shuō)為什么要維護(hù)一個(gè)Entry散列表來(lái)存儲(chǔ)內(nèi)容并以ThreadLocal對(duì)象作為key?

答:一個(gè)ThreadLocal對(duì)象只屬于一個(gè)線程,但一個(gè)線程可以實(shí)例化ThreadLocal對(duì)象。而ThreadLocalMap維護(hù)的數(shù)組存儲(chǔ)的就是以ThreadLocal實(shí)例作為key的Entry對(duì)象。

2、ThreadLocalMap中的Enery為什么要繼承WeakReference?

答:首先弱引用會(huì)在ThreadLocal對(duì)象不存在強(qiáng)引用的情況,弱引用對(duì)象會(huì)在下次GC時(shí)被清除。將ThreadLocal對(duì)象作為弱引用目的是為了防止內(nèi)存泄露。

假設(shè)Enery的key不是弱引用,即使在我們的代碼里threadLocal引用已失效,threadLocal也不會(huì)被GC,因?yàn)楫?dāng)前線程持有ThreadLocalMap的引用,而ThreadLocalMap持有Entry數(shù)組的引用,Entry對(duì)象的key又持有threadLocal的引用,threadLocal對(duì)象針對(duì)當(dāng)前線程可達(dá),所以不會(huì)被GC。

而Enery的key值threadLocal作為弱引用,在引用失效時(shí)會(huì)被GC。但即使threadLocal做為弱引用被GC清理,Entry[]還是存在entry對(duì)象,只是key為null,vlue對(duì)象也還存在,這些都是臟對(duì)象。弱引用不單是清理了threadLocal對(duì)象,它的另一層含義是可以標(biāo)識(shí)出Enery[]數(shù)組中哪些元素應(yīng)該被GC(我們這里稱為舊元素),然后程序里找出這些entry并清理。

ThreadLocalMap的set方法

回到前面提到的set方法,當(dāng)map不為null時(shí)會(huì)調(diào)用ThreadLocalMap的set方法。

ThreadLocalMap的set方法描述了如何將值散列到哈希表中,是開放地址法以線性探測(cè)方式散列的實(shí)現(xiàn)。在成功set值之后,嘗試清理一些舊元素,如果沒(méi)有發(fā)現(xiàn)舊元素則判斷閾值,確認(rèn)哈希表是否足夠大、是否需要擴(kuò)容。如果哈希表過(guò)于擁擠,get/set值會(huì)發(fā)生頻繁的沖突,這是不期望的情況。ThreadLocalMap的set方法代碼及詳細(xì)注釋如下

private void set(ThreadLocal<?> key, Object value) {// We do not use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.// 我們不像get()那樣先使用快速路徑(直接散列)判斷// 因?yàn)槭褂胹et()創(chuàng)建新元素至少與替換現(xiàn)有元素一樣頻繁,在這種情況下,散列后立刻判斷會(huì)容易失敗。// 所以直接先線性探測(cè)Entry[] tab = table;int len = tab.length;// 根據(jù)hashcode散列到數(shù)組位置int i = key.threadLocalHashCode & (len-1);// 開放地址法處理散列沖突,線性探測(cè)找到可以存放位置// 遍歷數(shù)組找到下一個(gè)可以存放元素的位置,這種位置包含三種情況// 1.元素的key已存在,直接賦值value// 2.元素的key位null,說(shuō)明k作為弱引用被GC清理,該位置為舊數(shù)據(jù),需要被替換// 3.直到遍歷到一個(gè)數(shù)組位置為null的位置賦值for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {//key已存在則直接更新e.value = value;return;}if (k == null) { //e不為null但k為null說(shuō)明k作為弱引用被GC,是舊數(shù)據(jù)需要被清理// i為舊數(shù)據(jù)位置,清理該位置并依據(jù)key合理地散列或?qū)alue替換到數(shù)組中// 然后重新散列i后面的元素,并順便清理i位置附近的其他舊元素replaceStaleEntry(key, value, i);return;}}// 遍歷到一個(gè)數(shù)組位置為null的位置賦值tab[i] = new Entry(key, value);int sz = ++size;// 調(diào)用cleanSomeSlots嘗試性發(fā)現(xiàn)并清理舊元素,如果沒(méi)有發(fā)現(xiàn)且舊元素當(dāng)前容量超過(guò)閾值,則調(diào)用rehashif (!cleanSomeSlots(i, sz) && sz >= threshold)// 此時(shí)認(rèn)為表空間不足,全量遍歷清理舊元素,清理后判斷容量若大于閾值的3/4,若是則擴(kuò)容并從新散列rehash();}

replaceStaleEntry方法

replaceStaleEntry方法是當(dāng)我們線性探測(cè)時(shí),如果碰到了舊元素就執(zhí)行。該方法做的事情比較多,可以總結(jié)為我們?cè)趕taleSlot位置發(fā)現(xiàn)舊元素,將新值覆蓋到staleSlot位置上并清理staleSlot附近的舊元素。“附近”指的是staleSlot位置前后連續(xù)的非null元素。代碼及詳細(xì)注釋如下

private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;// Back up to check for prior stale entry in current run.// We clean out whole runs at a time to avoid continual// incremental rehashing due to garbage collector freeing// up refs in bunches (i.e., whenever the collector runs).// 向前檢查是否存在舊元素,一次性徹底清理由于GC清除的弱引用key導(dǎo)致的舊數(shù)據(jù),避免多次執(zhí)行int slotToExpunge = staleSlot;// 向前遍歷找到entry不為空且key為null的位置賦值給slotToExpungefor (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;// Find either the key or trailing null slot of run, whichever// occurs first// staleSlot位置向后遍歷如果位置不為空,判斷key是否已經(jīng)存在// 回想前面我們是set實(shí)例的時(shí)候,碰到舊元素的情況下調(diào)用該方法,所以很可能在staleSlot后面key是已經(jīng)存在的for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();// If we find key, then we need to swap it// with the stale entry to maintain hash table order.// The newly stale slot, or any other stale slot// encountered above it, can then be sent to expungeStaleEntry// to remove or rehash all of the other entries in run.// 如果我們找到鍵,那么我們需要將它與舊元素交換以維護(hù)哈希表順序。// 然后可以將交換后得到的舊索引位置// 或其上方遇到的任何其他舊索引位置傳給expungeStaleEntry清理舊條// 如果碰到key相同的值則覆蓋valueif (k == key) {e.value = value;// i位置與staleSlot舊數(shù)據(jù)位置做交換,將數(shù)組元素位置規(guī)范化,維護(hù)哈希表順序// 這里維護(hù)哈希表順序是必要的,舉例來(lái)說(shuō),回想前面threadLocal.set實(shí)例的判斷,是線性探測(cè)找到可以賦值的位置// 如果哈希順序不維護(hù),可能造成同一個(gè)實(shí)例被賦值多次的情況// 包括后面清理舊元素的地方都要重新維護(hù)哈希表順序tab[i] = tab[staleSlot];tab[staleSlot] = e;// Start expunge at preceding stale entry if it exists// 開始清理前面的舊元素// 如果前面向前或向后查找的舊元素不存在,也就是slotToExpunge == staleSlot//此時(shí)slotToExpunge = i,此時(shí)位置i的元素是舊元素,需要被清理// slotToExpunge用來(lái)存儲(chǔ)第一個(gè)需要被清理的舊元素位置if (slotToExpunge == staleSlot)slotToExpunge = i;// 清理完slotToExpunge位置及其后面非空連續(xù)位置后,通過(guò)調(diào)用cleanSomeSlots嘗試性清理一些其他位置的舊元素// cleanSomeSlots不保證清理全部舊元素,它的時(shí)間復(fù)雜度O(log2n),他只是全量清理舊元素或不清理的折中cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}// If we do not find stale entry on backward scan, the// first stale entry seen while scanning for key is the// first still present in the run.// 如果前面向前查找的舊元素不存在,也就是slotToExpunge == staleSlot,而此時(shí)位置i為舊元素,所以將i賦值給slotToExpunge// slotToExpunge用來(lái)存儲(chǔ)第一個(gè)需要被清理的舊元素位置if (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}// If key not found, put new entry in stale slot// 如果向后遍歷非空entry都沒(méi)有找到key,則直接賦值給當(dāng)前staleSlot舊元素位置tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// If there are any other stale entries in run, expunge them// 通過(guò)前面根據(jù)staleSlot向前/向后遍歷,如果發(fā)現(xiàn)有舊元素則清理if (slotToExpunge != staleSlot)// 清理完slotToExpunge位置及其后面非空連續(xù)位置后,通過(guò)調(diào)用cleanSomeSlots嘗試性清理一些其他位置的舊元素// cleanSomeSlots不保證清理全部舊元素,它的時(shí)間復(fù)雜度O(log2n),他只是全量清理舊元素或不清理的折中cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}

expungeStaleEntry方法

查找到的舊元素都會(huì)執(zhí)行expungeStaleEntry方法。expungeStaleEntry頻繁被使用,它是清理舊元素的核心方法。該方法的做的事情就是:清理包括staleSlot位置后面連續(xù)為空元素中的所有舊元素并重新散列,返回staleSlot后面首個(gè)null位置。代碼及詳細(xì)注釋如下

private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlot// 清空staleSlot位置的元素tab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter null// 舊位置清理后,后面的元素需要重新散列到數(shù)組里,直到遇到數(shù)組位置為null。即維護(hù)哈希順序。Entry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) { // k == null說(shuō)明此位置也是舊數(shù)據(jù),需要清理e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);// 將staleSlot后面不為空位置重新散列,如果與當(dāng)前位置不同,則向前移動(dòng)到h位置后面(包括h)的首個(gè)空位置if (h != i) {tab[i] = null;// Unlike Knuth 6.4 Algorithm R, we must scan until// null because multiple entries could have been stale.while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}

cleanSomeSlots方法

cleanSomeSlots是一個(gè)比較靈動(dòng)的方法。就如他的名字"some"一樣。該方法只是嘗試性地尋找一些舊元素。添加新元素或替換舊元素時(shí)都會(huì)調(diào)用此方法。它的執(zhí)行復(fù)雜度log2(n),他是 “不清理”和“全量清理”的折中。若有發(fā)現(xiàn)舊元素返回true。代碼及詳細(xì)注釋如下

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);}// n >>>= 1無(wú)符號(hào)右移1位,即移動(dòng)次數(shù)以n的二進(jìn)制最高位的1的位置為基準(zhǔn)// 所以時(shí)間復(fù)雜度log2(n)} while ( (n >>>= 1) != 0);return removed;}

rehash/expungeStaleEntries/resize方法

在成功set值后,通過(guò)閾值判斷,如果程序認(rèn)為表空間不足就會(huì)調(diào)用rehash方法。

rehash做了兩件事,首先全量遍歷清理舊元素,然后在清理后判斷容量是否足夠,若成立則2倍擴(kuò)容并重新散列。

expungeStaleEntries則是全量清理舊元素,resize則是二倍擴(kuò)容。

// rehash全量地遍歷清理舊元素,然后判斷容量若大于閾值的3/4,則擴(kuò)容并從新散列// 程序認(rèn)為表空間不足時(shí)會(huì)調(diào)用該方法private void rehash() {// 全量遍歷清理舊元素expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresis// 適當(dāng)?shù)臄U(kuò)容,以避免hash散列到數(shù)組時(shí)過(guò)多的位置沖突if (size >= threshold - threshold / 4)// 2倍擴(kuò)容并重新散列resize();}// 全量遍歷清理舊元素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);}}// 二倍擴(kuò)容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;}

ThreadLocal的get方法

ThreadLocal的get邏輯相比set要簡(jiǎn)單的多。他只是將threadLocal對(duì)象散列到數(shù)組中,通過(guò)線性探測(cè)的方式找到匹配的值。代碼及詳細(xì)注釋如下

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初始化一個(gè)key為當(dāng)前threadLocal值為null的ThreadLocalMap對(duì)象return setInitialValue();}private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;else // 直接散列找不到的情況,調(diào)用getEntryAfterMiss線性探測(cè)查找期望元素return getEntryAfterMiss(key, i, e);}private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;// 線性探測(cè)找到符合的元素,若遇到舊元素則進(jìn)行清理while (e != null) {ThreadLocal<?> k = e.get();if (k == key)return e;if (k == null)expungeStaleEntry(i);elsei = nextIndex(i, len);e = tab[i];}return null;}

remove方法

remove即將引用清空并調(diào)用清理舊元素方法。所以remove不會(huì)產(chǎn)生舊元素,當(dāng)我們確認(rèn)哪些內(nèi)容需要移除時(shí)優(yōu)先使用remove方法清理,盡量不要交給GC處理。避免get/set發(fā)現(xiàn)舊元素的情況過(guò)多。

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}

“java ThreadLocal的源碼知識(shí)有哪些”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI