目錄
使用ThreadLocal
?例子
內部結構分析
源碼解析
?圖示詳解
ThreadLocal是Java中一個非常重要且常用的線程局部變量工具類,它使得每個線程可以獨立地持有自己的變量副本,而不是共享變量,解決了多線程環境下變量共享的線程安全問題。下面我將從多個維度深入分析ThreadLocal的內部結構和工作原理。
使用ThreadLocal
// 1. 初始化:創建ThreadLocal變量
private static ThreadLocal<T> threadLocal = new ThreadLocal<>();// 2. 設置值:為當前線程設置值
threadLocal.set(value); // value為要存儲的泛型對象// 3. 獲取值:獲取當前線程的值
T value = threadLocal.get(); // 返回當前線程存儲的值// 4. 移除值:清除當前線程的ThreadLocal變量(防止內存泄漏)
threadLocal.remove();
【注】使用時,通常將ThreadLocal聲明為
static final
以保證全局唯一性private static ThreadLocal<T> threadLocal = ThreadLocal.withInitial(() -> initialValue);
【注:】 withInitial里面放的是任何能夠返回 T 類型實例的 Lambda / Supplier。
只要 Supplier 的邏輯最終能 new(或從緩存、工廠、單例池等)拿出一個 T,就合法。
?例子
package com.qcby.test;import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class ThreadLocalTest {private List<String> messages = new ArrayList<>();public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new);public static void add(String message) {holder.get().messages.add(message);}public static List<String> clear() {List<String> messages = holder.get().messages;holder.remove();return messages;}public static void main(String[] args) throws InterruptedException {// 創建線程池ExecutorService executor = Executors.newFixedThreadPool(10);// 提交10個任務for (int i = 0; i < 10; i++) {final int threadId = i;executor.submit(() -> {ThreadLocalTest.add("線程" + threadId + "的消息" );// 打印當前線程的消息System.out.println("線程" + threadId + "的消息列表: " + holder.get().messages);// 清除當前線程的ThreadLocalThreadLocalTest.clear();});}// 關閉線程池executor.shutdown();executor.awaitTermination(1, TimeUnit.SECONDS);// 主線程檢查自己的ThreadLocal(應該是空的)System.out.println("主線程的消息列表: " + holder.get().messages);}
}
內部結構分析
根據這里get的源碼追溯分析:
追溯到:
源碼解析
/*** 獲取當前線程的ThreadLocal變量值*/
public T get() {// 1. 獲取當前線程對象Thread t = Thread.currentThread();// 2. 獲取當前線程的ThreadLocalMap(線程私有數據存儲結構)ThreadLocalMap map = getMap(t);// 3. 如果map已存在if (map != null) {// 3.1 以當前ThreadLocal實例為key(也就是代碼中的holder),獲取對應的EntryThreadLocalMap.Entry e = map.getEntry(this);// 3.2 如果Entry存在if (e != null) {// 3.2.1 強轉為泛型類型并返回值@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 4. 如果map不存在或未找到值,初始化并返回默認值return setInitialValue();
}/*** 獲取線程的ThreadLocalMap(實際是Thread類的threadLocals字段)*/
ThreadLocalMap getMap(Thread t) {return t.threadLocals; // 直接返回線程對象的成員變量
}/*** 初始化值并存入ThreadLocalMap*/
private T setInitialValue() {// 1. 獲取初始值(子類可重寫initialValue()方法)T value = initialValue();// 2. 獲取當前線程Thread t = Thread.currentThread();// 3. 獲取線程的ThreadLocalMapThreadLocalMap map = getMap(t);// 4. 如果map已存在,直接設置值if (map != null) {map.set(this, value);} else {// 5. 如果map不存在,創建新map并存入初始值createMap(t, value);}// 6. 返回初始值return value;
}/*** 創建線程的ThreadLocalMap并存入第一個值*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}/*** 默認初始值實現(可被withInitial覆蓋)*/
protected T initialValue() {return null; // 默認返回null
}
?圖示詳解
所以執行結果:
可以看見一個線程中只有一個信息,而不是它們統一堆砌在一起,原因就是底層是每個線程創建了一個Map對象,每個Map的value就是存入的messages本質是對象,也就是T--ThreadLocalTest對象們,并且它們Map中的Entry中的Key值都是一樣的,都是這個ThreadLocal,也就是holder。
【注】并不是每個線程的Map只能存放一個value對象,是這里我展示的例子里,一個線程只存了一條,完全可以存入很多條消息,然后add()時就會累加在Map已經創建好的Entry后面也就是:
當然既然是Map,存儲Entry就涉及Hash了,這個以后再詳談。