談?wù)勀銓?duì)ThreadLocal的理解

思考:對(duì)ThreadLocal的理解多少?
springboot葵花寶典
主要分享JAVA技術(shù),主要包含SpringBoot、SpingCloud、Docker、中間件等技術(shù),以及Github開源項(xiàng)目
1.ThreadLocal概述
ThreadLocal是多線程中對(duì)于解決線程安全的一個(gè)操作類,它會(huì)為每個(gè)線程都分配一個(gè)獨(dú)立的線程副本從而解決了變量并發(fā)訪問沖突的問題。ThreadLocal 同時(shí)實(shí)現(xiàn)了線程內(nèi)的資源共享
案例:使用JDBC操作數(shù)據(jù)庫(kù)時(shí),會(huì)將每一個(gè)線程的Connection放入各自的ThreadLocal中,從而保證每個(gè)線程都在各自的 Connection 上進(jìn)行數(shù)據(jù)庫(kù)的操作,避免A線程關(guān)閉了B線程的連接。
圖片
2. ThreadLocal的實(shí)現(xiàn)原理&源碼解析
ThreadLocal本質(zhì)來說就是一個(gè)線程內(nèi)部存儲(chǔ)類,從而讓多個(gè)線程只操作自己內(nèi)部的值,從而實(shí)現(xiàn)線程數(shù)據(jù)隔離
每個(gè)線程內(nèi)有一個(gè) ThreadLocalMap 類型的成員變量,用來存儲(chǔ)資源對(duì)象
圖片
ThreadLocalMap 的一些特點(diǎn)
key 的 hash 值統(tǒng)一分配
初始容量 16,擴(kuò)容因子 2/3,擴(kuò)容容量翻倍
key 索引沖突后用開放尋址法解決沖突
2.1. ThreadLocal基本使用
set(value) 設(shè)置值: 以 ThreadLocal 自己作為 key,資源對(duì)象作為 value,放入當(dāng)前線程的 ThreadLocalMap 集合中
get() 獲取值: 以 ThreadLocal 自己作為 key,到當(dāng)前線程中查找關(guān)聯(lián)的資源值
remove() 清除值: 以 ThreadLocal 自己作為 key,移除當(dāng)前線程關(guān)聯(lián)的資源值
代碼案例
public class ThreadLocalTest {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
String name = Thread.currentThread().getName();
threadLocal.set("zbbmeta");
print(name);
System.out.println(name + "-after remove : " + threadLocal.get());
}, "t1").start();
new Thread(() -> {
String name = Thread.currentThread().getName();
threadLocal.set("zbbmeta");
print(name);
System.out.println(name + "-after remove : " + threadLocal.get());
}, "t2").start();
}
static void print(String str) {
//打印當(dāng)前線程中本地內(nèi)存中本地變量的值
System.out.println(str + " :" + threadLocal.get());
//清除本地內(nèi)存中的本地變量
threadLocal.remove();
}
}3. ThreadLocal-內(nèi)存泄露問題
在介紹內(nèi)存泄露問題問題之前先介紹一下Java對(duì)象中的四種引用類型:Java對(duì)象中的四種引用類型:
- 強(qiáng)引用: 最為普通的引用方式,表示一個(gè)對(duì)象處于有用且必須的狀態(tài),如果一個(gè)對(duì)象具有強(qiáng)引用,則GC并不會(huì)回收它。即便堆中內(nèi)存不足了,寧可出現(xiàn)OOM,也不會(huì)對(duì)其進(jìn)行回收
Object obj = new Object();- 軟引用:軟引用用于描述一些還有用但并非必須保持的對(duì)象。在系統(tǒng)即將發(fā)生內(nèi)存溢出之前,垃圾收集器會(huì)清理這些軟引用指向的對(duì)象
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);- 弱引用:表示一個(gè)對(duì)象處于可能有用且非必須的狀態(tài)。在GC線程掃描內(nèi)存區(qū)域時(shí),一旦發(fā)現(xiàn)弱引用,就會(huì)回收到弱引用相關(guān)聯(lián)的對(duì)象。對(duì)于弱引用的回收,無關(guān)內(nèi)存區(qū)域是否足夠,一旦發(fā)現(xiàn)則會(huì)被回收
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);- 虛引用:虛引用也稱為幽靈引用,它幾乎沒有實(shí)際意義,主要用于跟蹤對(duì)象被垃圾收集的活動(dòng);虛引用不能單獨(dú)使用,必須與引用隊(duì)列(ReferenceQueue)一起使用。當(dāng)垃圾收集器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它有虛引用,會(huì)把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中
Object obj = new Object();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue);3.1. ThreadLocal-內(nèi)存泄露問題
每一個(gè)Thread維護(hù)一個(gè)ThreadLocalMap,在ThreadLocalMap中的Entry對(duì)象繼承了WeakReference,其中key為使用弱引用的ThreadLocal實(shí)例,value為線程變量的副本
圖片
ThreadLocalMap 中的 key 被設(shè)計(jì)為弱引用,原因如下
Thread 可能需要長(zhǎng)時(shí)間運(yùn)行(如線程池中的線程),如果 key 不再使用,需要在內(nèi)存不足(GC)時(shí)釋放其占用的內(nèi)存
內(nèi)存釋放時(shí)機(jī)
- 被動(dòng) GC 釋放 key
僅是讓 key 的內(nèi)存釋放,關(guān)聯(lián) value 的內(nèi)存并不會(huì)釋放
- 懶惰被動(dòng)釋放 value
- get key 時(shí),發(fā)現(xiàn)是 null key,則釋放其 value 內(nèi)存
- set key 時(shí),會(huì)使用啟發(fā)式掃描,清除臨近的 null key 的 value 內(nèi)存,啟發(fā)次數(shù)與元素個(gè)數(shù),是否發(fā)現(xiàn) null key 有關(guān)
- 主動(dòng) remove 釋放 key,value
- 會(huì)同時(shí)釋放 key,value 的內(nèi)存,也會(huì)清除臨近的 null key 的 value 內(nèi)存
- 推薦使用它,因?yàn)橐话闶褂?ThreadLocal 時(shí)都把它作為靜態(tài)變量(即強(qiáng)引用),因此無法被動(dòng)依靠 GC 回收
4. ThreadLocal面試題
面試官:談?wù)勀銓?duì)ThreadLocal的理解
候選人:
ThreadLocal 主要功能有兩個(gè):
- 第一個(gè)是可以實(shí)現(xiàn)資源對(duì)象的線程隔離,讓每個(gè)線程各用各的資源對(duì)象,避免爭(zhēng)用引發(fā)的線程安全問題
- 第二個(gè)是實(shí)現(xiàn)了線程內(nèi)的資源共享
面試官:好的,那你知道ThreadLocal的底層原理實(shí)現(xiàn)嗎?
候選人:
在ThreadLocal內(nèi)部維護(hù)了一個(gè)一個(gè) ThreadLocalMap 類型的成員變量,用來存儲(chǔ)資源對(duì)象
當(dāng)我們調(diào)用 set 方法,就是以 ThreadLocal 自己作為 key,資源對(duì)象作為 value,放入當(dāng)前線程的 ThreadLocalMap 集合中
當(dāng)調(diào)用 get 方法,就是以 ThreadLocal 自己作為 key,到當(dāng)前線程中查找關(guān)聯(lián)的資源值
當(dāng)調(diào)用 remove 方法,就是以 ThreadLocal 自己作為 key,移除當(dāng)前線程關(guān)聯(lián)的資源值
面試官:好的,那關(guān)于ThreadLocal會(huì)導(dǎo)致內(nèi)存溢出這個(gè)事情,了解嗎?
候選人:
是應(yīng)為ThreadLocalMap 中的 key 被設(shè)計(jì)為弱引用,它是被動(dòng)的被GC調(diào)用釋放key,不過關(guān)鍵的是只有key可以得到內(nèi)存釋放,而value不會(huì),因?yàn)関alue是一個(gè)強(qiáng)引用。
在使用ThreadLocal 時(shí)都把它作為靜態(tài)變量(即強(qiáng)引用),因此無法被動(dòng)依靠 GC 回收,建議主動(dòng)的remove 釋放 key,這樣就能避免內(nèi)存溢出。





































