說明
面試官:講講你對ThreadLocal的一些理解。
那么我們該怎么回答呢腰鬼?嵌赠?靴拱??你也可以思考下猾普,下面看看零度的思考袜炕;
ThreadLocal用在什么地方?
ThreadLocal一些細(xì)節(jié)初家!
ThreadLocal的最佳實踐偎窘!
思考
ThreadLocal用在什么地方?
討論ThreadLocal用在什么地方前溜在,我們先明確下陌知,如果僅僅就一個線程,那么都不用談ThreadLocal的掖肋,ThreadLocal是用在多線程的場景的F推稀!志笼!
ThreadLocal歸納下來就2類用途:
- 保存線程上下文信息沿盅,在任意需要的地方可以獲取H依!!窖铡!
- 線程安全的费彼,避免某些情況需要考慮線程安全必須同步帶來的性能損失9坎:缗ァ!
保存線程上下文信息,在任意需要的地方可以獲鹊憾肌>室摺!!
由于ThreadLocal的特性拔创,同一線程在某地方進(jìn)行設(shè)置剩燥,在隨后的任意地方都可以獲取到灭红。從而可以用來保存線程上下文信息比伏。
常用的比如每個請求怎么把一串后續(xù)關(guān)聯(lián)起來,就可以用ThreadLocal進(jìn)行set悠菜,在后續(xù)的任意需要記錄日志的方法里面進(jìn)行g(shù)et獲取到請求id悔醋,從而把整個請求串起來。
還有比如Spring的事務(wù)管理账阻,用ThreadLocal存儲Connection淘太,從而各個DAO可以獲取同一Connection撇贺,可以進(jìn)行事務(wù)回滾松嘶,提交等操作。
備注: ThreadLocal的這種用處蕴轨,很多時候是用在一些優(yōu)秀的框架里面的骇吭,一般我們很少接觸棘脐,反而下面的場景我們接觸的更多一些蛀缝!
線程安全的屈梁,避免某些情況需要考慮線程安全必須同步帶來的性能損失!9共浮曙强!
ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。但是ThreadLocal也有局限性臀防,我們來看看阿里規(guī)范:
每個線程往ThreadLocal中讀寫數(shù)據(jù)是線程隔離,互相之間不會影響的,所以ThreadLocal無法解決共享對象的更新問題嫌蚤!
由于不需要共享信息脱吱,自然就不存在競爭問題了,從而保證了某些情況下線程的安全宦搬,以及避免了某些情況需要考慮線程安全必須同步帶來的性能損失!c咀恪!
這類場景阿里規(guī)范里面也提到了:
ThreadLocal一些細(xì)節(jié)!
ThreaLocal使用示例代碼:
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
threadLocal.set(i);
System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
threadLocal.remove();
}
}, "threadLocal1").start();
new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
threadLocal.remove();
}
}, "threadLocal2").start();
}
}
代碼截圖:
代碼運行結(jié)果:
從運行的結(jié)果我們可以看到threadLocal1進(jìn)行set值對threadLocal2并沒有任何影響!
Thread畦徘、ThreadLocalMap关筒、ThreadLocal總覽圖
Thread類有屬性變量threadLocals (類型是ThreadLocal.ThreadLocalMap)袍榆,也就是說每個線程有一個自己的ThreadLocalMap ,所以每個線程往這個ThreadLocal中讀寫隔離的才写,并且是互相不會影響的锭硼。
一個ThreadLocal只能存儲一個Object對象轰异,如果需要存儲多個Object對象那么就需要多個ThreadLocal!Q栏巍雹姊!
如圖:
看到上面的幾個圖瘾境,大概思路應(yīng)該都清晰了,我們Entry的key指向ThreadLocal用虛線表示弱引用 懂更,下面我們來看看ThreadLocalMap:
java對象的引用包括 : 強(qiáng)引用慷暂,軟引用,弱引用,虛引用 。
因為這里涉及到弱引用座慰,簡單說明下:
弱引用也是用來描述非必需對象的,當(dāng)JVM進(jìn)行垃圾回收時翠拣,無論內(nèi)存是否充足版仔,該對象僅僅被弱引用關(guān)聯(lián),那么就會被回收误墓。
當(dāng)僅僅只有ThreadLocalMap中的Entry的key指向ThreadLocal的時候蛮粮,ThreadLocal會進(jìn)行回收的!S派铡蝉揍!
ThreadLocal被垃圾回收后,在ThreadLocalMap里對應(yīng)的Entry的鍵值會變成null畦娄,但是Entry是強(qiáng)引用又沾,那么Entry里面存儲的Object弊仪,并沒有辦法進(jìn)行回收,所以ThreadLocalMap 做了一些額外的回收工作杖刷。
雖然做了但是也會存在內(nèi)存泄漏風(fēng)險(我沒有遇到過励饵,網(wǎng)上很多類似場景,所以會提到后面的ThreadLocal最佳實踐;肌R厶!)
ThreadLocal的最佳實踐表窘!
ThreadLocal被垃圾回收后典予,在ThreadLocalMap里對應(yīng)的Entry的鍵值會變成null,但是Entry是強(qiáng)引用乐严,那么Entry里面存儲的Object瘤袖,并沒有辦法進(jìn)行回收,所以ThreadLocalMap 做了一些額外的回收工作昂验。
備注: 很多時候捂敌,我們都是用在線程池的場景,程序不停止既琴,線程基本不會銷毀U纪瘛!甫恩!
由于線程的生命周期很長逆济,如果我們往ThreadLocal里面set了很大很大的Object對象,雖然set填物、get等等方法在特定的條件會調(diào)用進(jìn)行額外的清理纹腌,但是ThreadLocal被垃圾回收后,在ThreadLocalMap里對應(yīng)的Entry的鍵值會變成null滞磺,但是后續(xù)在也沒有操作set升薯、get等方法了。
所以最佳實踐击困,應(yīng)該在我們不使用的時候涎劈,主動調(diào)用remove方法進(jìn)行清理。
這里把ThreadLocal定義為static還有一個好處就是阅茶,由于ThreadLocal有強(qiáng)引用在蛛枚,那么在ThreadLocalMap里對應(yīng)的Entry的鍵會永遠(yuǎn)存在,那么執(zhí)行remove的時候就可以正確進(jìn)行定位到并且刪除A嘲А1钠帧!
最佳實踐做法應(yīng)該為:
try {
// 其它業(yè)務(wù)邏輯
} finally {
threadLocal對象.remove();
}
思考
如果面試的時候撞蜂,可以把上面的內(nèi)容都可以講到盲镶,個人覺得就非常好了侥袜,回答的就挺完美了。但是如果你可以進(jìn)行下面的回答溉贿,那么就更完美了枫吧。
對于ThreadLocal,我在看Netty源碼的時候宇色,還了解過FastThreadLocal九杂,xxxxx一些列內(nèi)容,那就是一個升級了宣蠕。
在我本地進(jìn)行測試例隆,F(xiàn)astThreadLocal的吞吐量是jdkThreadLocal的3倍左右。
備注: 由于FastThreadLocal內(nèi)容也非常非常多植影,而且有很多技巧裳擎,所以準(zhǔn)備后續(xù)專門在開一篇進(jìn)行串起來!K急摇!