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