快速搞懂ThreadLocal实现原理
文章目錄
一、ThreadLocal使用案例
二、ThreadLocal類的實現原理
2.1 核心方法set()
2.2 核心方法get()
2.3 核心方法remove()
三、ThreadLocal.ThreadLocalMap結構分析
四、ThreadLocal內存泄漏問題
參考資料
維持線程封閉性可以通過Ad-hoc線程封閉、棧封閉來實現,一種更加規范的方法是使用ThreadLocal類。ThreadLocal類提供線程局部變量,通過get、set等方法訪問變量,為每個使用該變量的線程創建一個獨立的副本。
一、ThreadLocal使用案例
案例中只開啟了一個線程threadA,展示了在線程內部設置、獲取、清除局部變量。
public class ThreadLocalTest {
? ? // 初始化ThreadLocal變量
? ? static ThreadLocal<String> localVariable = new ThreadLocal<>();
? ? static void print(String str) {
? ? ? ? // 打印當前線程本地內存中的變量值
? ? ? ? System.out.println(str + ": " + localVariable.get());
? ? ? ? // 清除當前線程本地內存中的變量
? ? ? ? localVariable.remove();
? ? }
? ? public static void main(String[] args) {
? ? ? ? // 創建線程A
? ? ? ? Thread threadA = new Thread(new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? // 設置線程A中的本地變量的值
? ? ? ? ? ? ? ? localVariable.set("threadA localVariable");
? ? ? ? ? ? ? ? print("threadA");
? ? ? ? ? ? ? ? // 獲取線程A中的本地變量的值
? ? ? ? ? ? ? ? System.out.println("threadA remove after: " + localVariable.get());
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? // 啟動線程
? ? ? ? threadA.start();
? ? }
}
運行結果:
threadA: threadA localVariable
threadA remove after: null
案例二:線程唯一標識符生成器,為每個調用ThreadId.get()方法的線程創建id。
public class ThreadId {
? ? // 下一個要被分配的線程id
? ? private static final AtomicInteger nextId = new AtomicInteger(0);
? ? // 線程局部變量
? ? private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
? ? ? ? @Override
? ? ? ? protected Integer initialValue() {
? ? ? ? ? ? return nextId.getAndIncrement();
? ? ? ? }
? ? };
? ? // 返回當前線程唯一的id
? ? public static int get() {
? ? ? ? return threadId.get();
? ? }
}
二、ThreadLocal類的實現原理
在Thread類中有一個threadLocals成員變量,其類型是ThreadLocalMap,默認情況下為null。
ThreadLocal.ThreadLocalMap threadLocals = null;
1
當某線程首次調用ThreadLocal變量的get或set方法時,會進行對象創建。在線程退出時,當前線程的threadLocals變量被清空。
private void exit() {
? ? ...
? ? threadLocals = null;
? ? inheritableThreadLocals = null;
? ? ...
}
每個線程的局部變量不是存放于ThreadLocal實例中,而是存放于線程的threadLocals變量,即線程內存空間中。threadLocals變量本質上是Map數據結構,可以存放多個ThreadLocal變量鍵值對。
【助解】ThreadLocal類可以看出一個外殼,線程中調用某ThreadLocal變量的set方法可以將變量值放入到該線程的threadLocals變量中,數據格式是<當前線程中該ThreadLocal變量的this引用,變量值>。當調用線程調用ThreadLocal變量的get方法時,從當前線程的threadLocals變量中取出key(引用)對應的value值。
2.1 核心方法set()
將ThreadLocal變量的當前線程副本的值設置為指定value值。
public void set(T value) {
? ? // 獲取調用方法的當前線程
? ? Thread t = Thread.currentThread();
? ? // 獲取當前線程自身的threadLocals變量
? ? ThreadLocalMap map = getMap(t);
? ? if (map != null)?
? ? ? ? // map不為空,則設置
? ? ? ? map.set(this, value);
? ? else?
? ? ? ? // map為空,說明第一次調用,初始化線程的threadLocals變量
? ? ? ? createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
? ? return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
? ? t.threadLocals = new ThreadLocalMap(this, firstValue);
}
線程的threadLocals變量,即ThreadLocal.ThreadLocalMap,是HashMap結構,它的key是當前ThreadLocal的實例對象引用,value值是該ThreadLocal實例對象調用set方法設置的值。
2.2 核心方法get()
返回ThreadLocal變量在當前線程副本中的值。如果當前線程中沒有該變量的值,返回值會被首次初始化為initialValue()方法的值。
public T get() {
? ? // 獲取當前線程以及其threadLocals變量
? ? Thread t = Thread.currentThread();
? ? ThreadLocalMap map = getMap(t);
? ? // 如果threadLocals變量不為空
? ? if (map != null) {
? ? ? ? // 根據當前ThreadLocal對象應用獲取Entry,存在則直接返回value值
? ? ? ? ThreadLocalMap.Entry e = map.getEntry(this);
? ? ? ? if (e != null) {
? ? ? ? ? ? @SuppressWarnings("unchecked")
? ? ? ? ? ? T result = (T)e.value;
? ? ? ? ? ? return result;
? ? ? ? }
? ? }
? ? // threadLocals為空,初始化當前線程threadLocals變量
? ? return setInitialValue();
}
// threadLocals存在,設置初始值;不存在,初始化threadLocals變量
private T setInitialValue() {
? ? // 返回當前ThreadLocal變量的當前線程初始值
? ? T value = initialValue();
? ? Thread t = Thread.currentThread();
? ? ThreadLocalMap map = getMap(t);
? ? if (map != null)
? ? ? ? map.set(this, value);
? ? else
? ? ? ? createMap(t, value);
? ? return value;
}
protected T initialValue() {
? ? return null;
}
2.3 核心方法remove()
當前線程threadLocals變量存在的話,刪除當前線程的ThreadLocal實例對象。
public void remove() {
? ? ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
? ? if (var1 != null) {
? ? ? ? var1.remove(this);
? ? }
}
三、ThreadLocal.ThreadLocalMap結構分析
ThreadLocalMap內部類實現細節,由于內容較多獨立成了一篇博客。ThreadLocal.ThreadLocalMap實現細節
四、ThreadLocal內存泄漏問題
每個線程的ThreadLocal變量都存放在該線程的threadLocals變量中,如果當前線程一直不退出,這些ThreadLocal變量會一直存在,因此可能會導致內存泄漏。通過調用ThreadLocal類的remove方法避免這一問題。
ThreadLocalMap中采用ThreadLocal弱引用作為Entry的key,如果一個ThreadLocal沒有外部強引用來引用它,下一次系統GC時,這個ThreadLocal必然會被回收,ThreadLocalMap中就會出現key為null的Entry。
ThreadLocal類的set、get、remove方法都可能觸發對key為null的Entry清理操作。expungeStaleEntry方法會清空Entry及其value,Entry會在下次GC被回收。
如果當前線程一直在運行,并且一直不執行get、set、remove方法,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,導致這些key為null的Entry的value永遠無法回收,造成內存泄漏。
參考資料
《Java并發編程實戰》
《Java并發編程之美》
?
總結
以上是生活随笔為你收集整理的快速搞懂ThreadLocal实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 附马开痹片_功效作用注意事项用药禁忌用法
- 下一篇: 用贝叶斯来看看抛硬币的概率