對ThreadLocal的一些理解
ThreadLocal也是在面試過程中經常被問到的,本文主要從以下三個方面來談對ThreadLocal的一些理解:
- ThreadLocal用在什麼地方
- ThreadLocal一些細節
- ThreadLocal的最佳實踐
ThreadLocal用在什麼地方?
討論ThreadLocal用在什麼地方前,我們先明確下,如果僅僅就只有一個執行緒,那麼都不用談ThreadLocal,ThreadLocal是用在多執行緒的場景的
ThreadLocal歸納下來就2類用途:
- 儲存執行緒上下文資訊,在任意需要的地方可以獲取
- 執行緒安全的,避免某些情況需要考慮執行緒安全必須同步帶來的效能損失
儲存執行緒上下文資訊,在任意需要的地方可以獲取
由於ThreadLocal的特性,同一執行緒在某地方進行設定,在隨後的任意地方都可以獲取到。從而可以用來儲存執行緒上下文資訊。
常用的比如每個請求怎麼把一串後續關聯起來,就可以用ThreadLocal進行set,在後續的任意需要記錄日誌的方法裡面進行get獲取到請求id,從而把整個請求串起來。
還有比如Spring的事務管理,用ThreadLocal儲存Connection,從而各個DAO可以獲取同一Connection,可以進行事務回滾,提交等操作。
hreadLocal的這種用處,很多時候是用在一些優秀的框架裡面的,一般我們很少接觸,反而下面的場景我們接觸的更多一些!
執行緒安全的,避免某些情況需要考慮執行緒安全必須同步帶來的效能損失
ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。但是ThreadLocal也有侷限性,我們來看看阿里規範:
每個執行緒往ThreadLocal中讀寫資料是執行緒隔離,互相之間不會影響的,所以ThreadLocal無法解決共享物件的
更新問題!
由於不需要共享資訊,自然就不存在競爭問題了,從而保證了某些情況下執行緒的安全,以及避免了某些情況需要考慮執行緒安全必須同步帶來的效能損失
這類場景阿里規範裡面也提到了:
ThreadLocal一些細節
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(); } }
程式碼執行結果:
從執行的結果我們可以看到threadLocal1進行set值對threadLocal2並沒有任何影響!
Thread、ThreadLocalMap、ThreadLocal總覽圖:
Thread類有屬性變數threadLocals (型別是ThreadLocal.ThreadLocalMap),也就是說每個執行緒有一個自己的ThreadLocalMap ,所以每個執行緒往這個ThreadLocal中讀寫隔離的,並且是互相不會影響的。
一個ThreadLocal只能儲存一個Object物件,如果需要儲存多個Object物件那麼就需要多個ThreadLocal
如圖:
看到上面的幾個圖,大概思路應該都清晰了,我們Entry的key指向ThreadLocal用虛線表示弱引用 ,下面我們來看看ThreadLocalMap:
java物件的引用包括 :強引用,軟引用,弱引用,虛引用 。
因為這裡涉及到弱引用,簡單說明下:
弱引用是用來描述非必需物件的,當JVM進行垃圾回收時,無論記憶體是否充足,如果該物件僅僅被弱引用關聯,那麼就會被回收。
當僅僅只有ThreadLocalMap中的Entry的key指向ThreadLocal的時候,ThreadLocal會進行回收的
ThreadLocal被垃圾回收後,在ThreadLocalMap裡對應的Entry的鍵值會變成null,但是Entry是強引用,那麼Entry裡面儲存的Object,並沒有辦法進行回收,所以ThreadLocalMap 做了一些額外的回收工作。
ThreadLocal的最佳實踐!
很多時候,我們都是用線上程池的場景,程式不停止,執行緒基本不會銷燬
由於執行緒的生命週期很長,如果我們往ThreadLocal裡面set了很大很大的Object物件,雖然set、get等等方法在特定的條件會呼叫進行額外的清理,但是ThreadLocal被垃圾收集器回收後,在ThreadLocalMap裡對應的Entry的鍵會變成null,但是後續在也沒有操作set、get等方法了。
所以最佳實踐,應該在我們不使用的時候,主動呼叫remove方法進行清理。
這裡把ThreadLocal定義為static還有一個好處就是,由於ThreadLocal有強引用在,那麼在ThreadLocalMap裡對應的Entry的鍵會永遠存在,那麼執行remove的時候就可以正確進行定位到並且刪除!!!
最佳實踐做法應該為:
抽象為:
try {
// 其它業務邏輯
} finally {
threadLocal物件.remove();
}
思考
如果面試的時候,可以把上面的內容都可以講到,個人覺得就非常好了,回答的就挺完美了。但是如果你可以進行下面的回答,那麼就更完美了。
對於ThreadLocal,我在看Netty原始碼的時候,還了解過FastThreadLocal,xxxxx列一些內容。那就是一個升級了。實際上,FastThreadLocal的吞吐量比ThreadLocal高很多