<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
ThreadLocal 提供了一種方式,讓在多執行緒環境下,每個執行緒都可以擁有自己獨特的資料,並且可以在整個執行緒執行過程中,從上而下的傳遞。
可能很多同學沒有使用過 ThreadLocal,我們先來演示下 ThreadLocal 的用法,demo 如下:
/** * ThreadLocal 中儲存的資料是 Map */ static final ThreadLocal<Map<String, String>> context = new ThreadLocal<>(); @Test public void testThread() { // 從上下文中拿出 Map Map<String, String> contextMap = context.get(); if (CollectionUtils.isEmpty(contextMap)) { contextMap = Maps.newHashMap(); } contextMap.put("key1", "value1"); context.set(contextMap); log.info("key1,value1被放到上下文中"); // 從上下文中拿出剛才放進去的資料 getFromComtext(); } private String getFromComtext() { String value1 = context.get().get("key1"); log.info("從 ThreadLocal 中取出上下文,key1 對應的值為:{}", value1); return value1; } //執行結果: demo.ninth.ThreadLocalDemo - key1,value1被放到上下文中 demo.ninth.ThreadLocalDemo - 從 ThreadLocal 中取出上下文,key1 對應的值為:value1
從執行結果中可以看到,key1 對應的值已經從上下文中拿到了。
getFromComtext 方法是沒有接受任何入參的,通過 context.get().get(“key1”) 這行程式碼就從上下文中拿到了 key1 的值,接下來我們一起來看下 ThreadLocal 底層是如何實現上下文的傳遞的。
ThreadLocal 定義類時帶有泛型,說明 ThreadLocal 可以儲存任意格式的資料,原始碼如下:
public class ThreadLocal<T> {}
ThreadLocal 有幾個關鍵屬性,我們一一看下:
// threadLocalHashCode 表示當前 ThreadLocal 的 hashCode,用於計算當前 ThreadLocal 在 ThreadLocalMap 中的索引位置 private final int threadLocalHashCode = nextHashCode(); // 計算 ThreadLocal 的 hashCode 值(就是遞增) private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } // static + AtomicInteger 保證了在一臺機器中每個 ThreadLocal 的 threadLocalHashCode 是唯一的 // 被 static 修飾非常關鍵,因為一個執行緒在處理業務的過程中,ThreadLocalMap 是會被 set 多個 ThreadLocal 的,多個 ThreadLocal 就依靠 threadLocalHashCode 進行區分 private static AtomicInteger nextHashCode = new AtomicInteger();
還有一個重要屬性:ThreadLocalMap,當一個執行緒有多個 ThreadLocal 時,需要一個容器來管理多個 ThreadLocal,ThreadLocalMap 的作用就是這個,管理執行緒中多個 ThreadLocal。
ThreadLocalMap 本身就是一個簡單的 Map 結構,key 是 ThreadLocal,value 是 ThreadLocal 儲存的值,底層是陣列的資料結構,原始碼如下:
// threadLocalHashCode 表示當前 ThreadLocal 的 hashCode,用於計算當前 ThreadLocal 在 ThreadLocalMap 中的索引位置 private final int threadLocalHashCode = nextHashCode(); // 計算 ThreadLocal 的 hashCode 值(就是遞增) private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } // static + AtomicInteger 保證了在一臺機器中每個 ThreadLocal 的 threadLocalHashCode 是唯一的 // 被 static 修飾非常關鍵,因為一個執行緒在處理業務的過程中,ThreadLocalMap 是會被 set 多個 ThreadLocal 的,多個 ThreadLocal 就依靠 threadLocalHashCode 進行區分 private static AtomicInteger nextHashCode = new AtomicInteger();
從原始碼中看到 ThreadLocalMap 其實就是一個簡單的 Map 結構,底層是陣列,有初始化大小,也有擴容閾值大小,陣列的元素是 Entry,Entry 的 key 就是 ThreadLocal 的參照,value 是 ThreadLocal 的值。
ThreadLocal 是執行緒安全的,我們可以放心使用,主要因為是 ThreadLocalMap 是執行緒的屬性,我們看下執行緒 Thread 的原始碼,如下:
從上圖中,我們可以看到 ThreadLocals.ThreadLocalMap 和 InheritableThreadLocals.ThreadLocalMap 分別是執行緒的屬性,所以每個執行緒的 ThreadLocals 都是隔離獨享的。
父執行緒在建立子執行緒的情況下,會拷貝 inheritableThreadLocals 的值,但不會拷貝 threadLocals 的值,原始碼如下:
從上圖中我們可以看到,線上程建立時,會把父執行緒的 inheritableThreadLocals 屬性值進行拷貝。
set 方法的主要作用是往當前 ThreadLocal 裡面 set 值,假如當前 ThreadLocal 的泛型是 Map,那麼就是往當前 ThreadLocal 裡面 set map,原始碼如下:
// set 操作每個執行緒都是序列的,不會有執行緒安全的問題 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 當前 thradLocal 之前有設定值,直接設定,否則初始化 if (map != null) map.set(this, value); // 初始化ThreadLocalMap else createMap(t, value); }
程式碼邏輯比較清晰,我們在一起來看下 ThreadLocalMap.set 的原始碼,如下:
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; // 計算 key 在陣列中的下標,其實就是 ThreadLocal 的 hashCode 和陣列大小-1取餘 int i = key.threadLocalHashCode & (len-1); // 整體策略:檢視 i 索引位置有沒有值,有值的話,索引位置 + 1,直到找到沒有值的位置 // 這種解決 hash 衝突的策略,也導致了其在 get 時查詢策略有所不同,體現在 getEntryAfterMiss 中 for (Entry e = tab[i]; e != null; // nextIndex 就是讓在不超過陣列長度的基礎上,把陣列的索引位置 + 1 e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // 找到記憶體地址一樣的 ThreadLocal,直接替換 if (k == key) { e.value = value; return; } // 當前 key 是 null,說明 ThreadLocal 被清理了,直接替換掉 if (k == null) { replaceStaleEntry(key, value, i); return; } } // 當前 i 位置是無值的,可以被當前 thradLocal 使用 tab[i] = new Entry(key, value); int sz = ++size; // 當陣列大小大於等於擴容閾值(陣列大小的三分之二)時,進行擴容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
上面原始碼我們注意幾點:
好在日常工作中使用 ThreadLocal 時,常常只使用 1~2 個 ThreadLocal,通過 hash 計算出重複的陣列的概率並不是很大。
set 時的解決陣列元素位置衝突的策略,也對 get 方法產生了影響,接著我們一起來看一下 get 方法。
get 方法主要是從 ThreadLocalMap 中拿到當前 ThreadLocal 儲存的值,原始碼如下:
public T get() { // 因為 threadLocal 屬於執行緒的屬性,所以需要先把當前執行緒拿出來 Thread t = Thread.currentThread(); // 從執行緒中拿到 ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { // 從 map 中拿到 entry,由於 ThreadLocalMap 在 set 時的 hash 衝突的策略不同,導致拿的時候邏輯也不太一樣 ThreadLocalMap.Entry e = map.getEntry(this); // 如果不為空,讀取當前 ThreadLocal 中儲存的值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 否則給當前執行緒的 ThreadLocal 初始化,並返回初始值 null return setInitialValue(); }
接著我們來看下 ThreadLocalMap 的 getEntry 方法,原始碼如下:
// 得到當前 thradLocal 對應的值,值的型別是由 thradLocal 的泛型決定的 // 由於 thradLocalMap set 時解決陣列索引位置衝突的邏輯,導致 thradLocalMap get 時的邏輯也是對應的 // 首先嚐試根據 hashcode 取模陣列大小-1 = 索引位置 i 尋找,找不到的話,自旋把 i+1,直到找到索引位置不為空為止 private Entry getEntry(ThreadLocal<?> key) { // 計算索引位置:ThreadLocal 的 hashCode 取模陣列大小-1 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // e 不為空,並且 e 的 ThreadLocal 的記憶體地址和 key 相同,直接返回,否則就是沒有找到,繼續通過 getEntryAfterMiss 方法找 if (e != null && e.get() == key) return e; else // 這個取資料的邏輯,是因為 set 時陣列索引位置衝突造成的 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 時,其實還蠻耗效能的 while (e != null) { ThreadLocal<?> k = e.get(); // 記憶體地址一樣,表示找到了 if (k == key) return e; // 刪除沒用的 key if (k == null) expungeStaleEntry(i); // 繼續使索引位置 + 1 else i = nextIndex(i, len); e = tab[i]; } return null; }
get 邏輯原始碼中註釋已經寫的很清楚了,我們就不重複說了。
ThreadLocalMap 中的 ThreadLocal 的個數超過閾值時,ThreadLocalMap 就要開始擴容了,我們一起來看下擴容的邏輯:
//擴容 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 { // 計算 ThreadLocal 在新陣列中的位置 int h = k.threadLocalHashCode & (newLen - 1); // 如果索引 h 的位置值不為空,往後+1,直到找到值為空的索引位置 while (newTab[h] != null) h = nextIndex(h, newLen); // 給新陣列賦值 newTab[h] = e; count++; } } } // 給新陣列初始化下次擴容閾值,為陣列長度的三分之二 setThreshold(newLen); size = count; table = newTab; }
原始碼註解也比較清晰,我們注意兩點:
ThreadLocal 是非常重要的 API,我們在寫一箇中介軟體的時候經常會用到,比如說流程引擎中上下文的傳遞,呼叫鏈ID的傳遞等等,非常好用,但坑也很多。
以上就是java程式設計ThreadLocal上下傳遞原始碼解析的詳細內容,更多關於java程式設計ThreadLocal上下傳遞的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45