這篇文章主要介紹“Java編程中的ThreadLocal怎么使用”,在日常操作中,相信很多人在Java編程中的ThreadLocal怎么使用問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Java編程中的ThreadLocal怎么使用”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
成都創(chuàng)新互聯(lián)是專業(yè)的固鎮(zhèn)網(wǎng)站建設(shè)公司,固鎮(zhèn)接單;提供成都網(wǎng)站制作、做網(wǎng)站,網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行固鎮(zhèn)網(wǎng)站開發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!
ThreadLocal 提供了一種方式,讓在多線程環(huán)境下,每個(gè)線程都可以擁有自己獨(dú)特的數(shù)據(jù),并且可以在整個(gè)線程執(zhí)行過(guò)程中,從上而下的傳遞。
可能很多同學(xué)沒(méi)有使用過(guò) ThreadLocal,我們先來(lái)演示下 ThreadLocal 的用法,demo 如下:
/** * ThreadLocal 中保存的數(shù)據(jù)是 Map */ static final ThreadLocal
從運(yùn)行結(jié)果中可以看到,key1 對(duì)應(yīng)的值已經(jīng)從上下文中拿到了。
getFromComtext 方法是沒(méi)有接受任何入?yún)⒌?,通過(guò) context.get().get(“key1”) 這行代碼就從上下文中拿到了 key1 的值,接下來(lái)我們一起來(lái)看下 ThreadLocal 底層是如何實(shí)現(xiàn)上下文的傳遞的。
ThreadLocal 定義類時(shí)帶有泛型,說(shuō)明 ThreadLocal 可以儲(chǔ)存任意格式的數(shù)據(jù),源碼如下:
public class ThreadLocal{}
ThreadLocal 有幾個(gè)關(guān)鍵屬性,我們一一看下:
// threadLocalHashCode 表示當(dāng)前 ThreadLocal 的 hashCode,用于計(jì)算當(dāng)前 ThreadLocal 在 ThreadLocalMap 中的索引位置 private final int threadLocalHashCode = nextHashCode(); // 計(jì)算 ThreadLocal 的 hashCode 值(就是遞增) private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } // static + AtomicInteger 保證了在一臺(tái)機(jī)器中每個(gè) ThreadLocal 的 threadLocalHashCode 是唯一的 // 被 static 修飾非常關(guān)鍵,因?yàn)橐粋€(gè)線程在處理業(yè)務(wù)的過(guò)程中,ThreadLocalMap 是會(huì)被 set 多個(gè) ThreadLocal 的,多個(gè) ThreadLocal 就依靠 threadLocalHashCode 進(jìn)行區(qū)分 private static AtomicInteger nextHashCode = new AtomicInteger();
還有一個(gè)重要屬性:ThreadLocalMap,當(dāng)一個(gè)線程有多個(gè) ThreadLocal 時(shí),需要一個(gè)容器來(lái)管理多個(gè) ThreadLocal,ThreadLocalMap 的作用就是這個(gè),管理線程中多個(gè) ThreadLocal。
ThreadLocalMap 本身就是一個(gè)簡(jiǎn)單的 Map 結(jié)構(gòu),key 是 ThreadLocal,value 是 ThreadLocal 保存的值,底層是數(shù)組的數(shù)據(jù)結(jié)構(gòu),源碼如下:
// threadLocalHashCode 表示當(dāng)前 ThreadLocal 的 hashCode,用于計(jì)算當(dāng)前 ThreadLocal 在 ThreadLocalMap 中的索引位置 private final int threadLocalHashCode = nextHashCode(); // 計(jì)算 ThreadLocal 的 hashCode 值(就是遞增) private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } // static + AtomicInteger 保證了在一臺(tái)機(jī)器中每個(gè) ThreadLocal 的 threadLocalHashCode 是唯一的 // 被 static 修飾非常關(guān)鍵,因?yàn)橐粋€(gè)線程在處理業(yè)務(wù)的過(guò)程中,ThreadLocalMap 是會(huì)被 set 多個(gè) ThreadLocal 的,多個(gè) ThreadLocal 就依靠 threadLocalHashCode 進(jìn)行區(qū)分 private static AtomicInteger nextHashCode = new AtomicInteger();
從源碼中看到 ThreadLocalMap 其實(shí)就是一個(gè)簡(jiǎn)單的 Map 結(jié)構(gòu),底層是數(shù)組,有初始化大小,也有擴(kuò)容閾值大小,數(shù)組的元素是 Entry,Entry 的 key 就是 ThreadLocal 的引用,value 是 ThreadLocal 的值。
ThreadLocal 是線程安全的,我們可以放心使用,主要因?yàn)槭?ThreadLocalMap 是線程的屬性,我們看下線程 Thread 的源碼,如下:
從上圖中,我們可以看到 ThreadLocals.ThreadLocalMap 和 InheritableThreadLocals.ThreadLocalMap 分別是線程的屬性,所以每個(gè)線程的 ThreadLocals 都是隔離獨(dú)享的。
父線程在創(chuàng)建子線程的情況下,會(huì)拷貝 inheritableThreadLocals 的值,但不會(huì)拷貝 threadLocals 的值,源碼如下:
從上圖中我們可以看到,在線程創(chuàng)建時(shí),會(huì)把父線程的 inheritableThreadLocals 屬性值進(jìn)行拷貝。
set 方法的主要作用是往當(dāng)前 ThreadLocal 里面 set 值,假如當(dāng)前 ThreadLocal 的泛型是 Map,那么就是往當(dāng)前 ThreadLocal 里面 set map,源碼如下:
// set 操作每個(gè)線程都是串行的,不會(huì)有線程安全的問(wèn)題 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 當(dāng)前 thradLocal 之前有設(shè)置值,直接設(shè)置,否則初始化 if (map != null) map.set(this, value); // 初始化ThreadLocalMap else createMap(t, value); }
代碼邏輯比較清晰,我們?cè)谝黄饋?lái)看下 ThreadLocalMap.set 的源碼,如下:
private void set(ThreadLocal> key, Object value) { Entry[] tab = table; int len = tab.length; // 計(jì)算 key 在數(shù)組中的下標(biāo),其實(shí)就是 ThreadLocal 的 hashCode 和數(shù)組大小-1取余 int i = key.threadLocalHashCode & (len-1); // 整體策略:查看 i 索引位置有沒(méi)有值,有值的話,索引位置 + 1,直到找到?jīng)]有值的位置 // 這種解決 hash 沖突的策略,也導(dǎo)致了其在 get 時(shí)查找策略有所不同,體現(xiàn)在 getEntryAfterMiss 中 for (Entry e = tab[i]; e != null; // nextIndex 就是讓在不超過(guò)數(shù)組長(zhǎng)度的基礎(chǔ)上,把數(shù)組的索引位置 + 1 e = tab[i = nextIndex(i, len)]) { ThreadLocal> k = e.get(); // 找到內(nèi)存地址一樣的 ThreadLocal,直接替換 if (k == key) { e.value = value; return; } // 當(dāng)前 key 是 null,說(shuō)明 ThreadLocal 被清理了,直接替換掉 if (k == null) { replaceStaleEntry(key, value, i); return; } } // 當(dāng)前 i 位置是無(wú)值的,可以被當(dāng)前 thradLocal 使用 tab[i] = new Entry(key, value); int sz = ++size; // 當(dāng)數(shù)組大小大于等于擴(kuò)容閾值(數(shù)組大小的三分之二)時(shí),進(jìn)行擴(kuò)容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
上面源碼我們注意幾點(diǎn):
是通過(guò)遞增的 AtomicInteger 作為 ThreadLocal 的 hashCode 的;
計(jì)算數(shù)組索引位置的公式是:hashCode 取模數(shù)組大小,由于 hashCode 不斷自增,所以不同的 hashCode 大概率上會(huì)計(jì)算到同一個(gè)數(shù)組的索引位置(但這個(gè)不用擔(dān)心,在實(shí)際項(xiàng)目中,ThreadLocal 都很少,基本上不會(huì)沖突);
通過(guò) hashCode 計(jì)算的索引位置 i 處如果已經(jīng)有值了,會(huì)從 i 開始,通過(guò) +1 不斷的往后尋找,直到找到索引位置為空的地方,把當(dāng)前 ThreadLocal 作為 key 放進(jìn)去。
好在日常工作中使用 ThreadLocal 時(shí),常常只使用 1~2 個(gè) ThreadLocal,通過(guò) hash 計(jì)算出重復(fù)的數(shù)組的概率并不是很大。
set 時(shí)的解決數(shù)組元素位置沖突的策略,也對(duì) get 方法產(chǎn)生了影響,接著我們一起來(lái)看一下 get 方法。
get 方法主要是從 ThreadLocalMap 中拿到當(dāng)前 ThreadLocal 儲(chǔ)存的值,源碼如下:
public T get() { // 因?yàn)?nbsp;threadLocal 屬于線程的屬性,所以需要先把當(dāng)前線程拿出來(lái) Thread t = Thread.currentThread(); // 從線程中拿到 ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { // 從 map 中拿到 entry,由于 ThreadLocalMap 在 set 時(shí)的 hash 沖突的策略不同,導(dǎo)致拿的時(shí)候邏輯也不太一樣 ThreadLocalMap.Entry e = map.getEntry(this); // 如果不為空,讀取當(dāng)前 ThreadLocal 中保存的值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 否則給當(dāng)前線程的 ThreadLocal 初始化,并返回初始值 null return setInitialValue(); }
接著我們來(lái)看下 ThreadLocalMap 的 getEntry 方法,源碼如下:
// 得到當(dāng)前 thradLocal 對(duì)應(yīng)的值,值的類型是由 thradLocal 的泛型決定的 // 由于 thradLocalMap set 時(shí)解決數(shù)組索引位置沖突的邏輯,導(dǎo)致 thradLocalMap get 時(shí)的邏輯也是對(duì)應(yīng)的 // 首先嘗試根據(jù) hashcode 取模數(shù)組大小-1 = 索引位置 i 尋找,找不到的話,自旋把 i+1,直到找到索引位置不為空為止 private Entry getEntry(ThreadLocal> key) { // 計(jì)算索引位置:ThreadLocal 的 hashCode 取模數(shù)組大小-1 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // e 不為空,并且 e 的 ThreadLocal 的內(nèi)存地址和 key 相同,直接返回,否則就是沒(méi)有找到,繼續(xù)通過(guò) getEntryAfterMiss 方法找 if (e != null && e.get() == key) return e; else // 這個(gè)取數(shù)據(jù)的邏輯,是因?yàn)?nbsp;set 時(shí)數(shù)組索引位置沖突造成的 return getEntryAfterMiss(key, i, e); }
// 自旋 i+1,直到找到為止 private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // 在大量使用不同 key 的 ThreadLocal 時(shí),其實(shí)還蠻耗性能的 while (e != null) { ThreadLocal> k = e.get(); // 內(nèi)存地址一樣,表示找到了 if (k == key) return e; // 刪除沒(méi)用的 key if (k == null) expungeStaleEntry(i); // 繼續(xù)使索引位置 + 1 else i = nextIndex(i, len); e = tab[i]; } return null; }
get 邏輯源碼中注釋已經(jīng)寫的很清楚了,我們就不重復(fù)說(shuō)了。
ThreadLocalMap 中的 ThreadLocal 的個(gè)數(shù)超過(guò)閾值時(shí),ThreadLocalMap 就要開始擴(kuò)容了,我們一起來(lái)看下擴(kuò)容的邏輯:
//擴(kuò)容 private void resize() { // 拿出舊的數(shù)組 Entry[] oldTab = table; int oldLen = oldTab.length; // 新數(shù)組的大小為老數(shù)組的兩倍 int newLen = oldLen * 2; // 初始化新數(shù)組 Entry[] newTab = new Entry[newLen]; int count = 0; // 老數(shù)組的值拷貝到新數(shù)組上 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 { // 計(jì)算 ThreadLocal 在新數(shù)組中的位置 int h = k.threadLocalHashCode & (newLen - 1); // 如果索引 h 的位置值不為空,往后+1,直到找到值為空的索引位置 while (newTab[h] != null) h = nextIndex(h, newLen); // 給新數(shù)組賦值 newTab[h] = e; count++; } } } // 給新數(shù)組初始化下次擴(kuò)容閾值,為數(shù)組長(zhǎng)度的三分之二 setThreshold(newLen); size = count; table = newTab; }
源碼注解也比較清晰,我們注意兩點(diǎn):
擴(kuò)容后數(shù)組大小是原來(lái)數(shù)組的兩倍;
擴(kuò)容時(shí)是絕對(duì)沒(méi)有線程安全問(wèn)題的,因?yàn)?ThreadLocalMap 是線程的一個(gè)屬性,一個(gè)線程同一時(shí)刻只能對(duì) ThreadLocalMap 進(jìn)行操作,因?yàn)橥粋€(gè)線程執(zhí)行業(yè)務(wù)邏輯必然是串行的,那么操作 ThreadLocalMap 必然也是串行的。
到此,關(guān)于“Java編程中的ThreadLocal怎么使用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!