深入理解java:線程本地變量 java.lang.ThreadLocal類

hreadLocal,很多人都叫它做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。

可能很多朋友都知道ThreadLocal爲變量在每個線程中都創建了一個副本,那樣每個線程可以訪問自己內部的副本變量。

這句話從表面上看起來理解正確,但實際上這種理解是不太正確的。下面我們細細道來。

多線程併發執行時,需要數據共享,因此纔有了volatile變量解決 多線程間的數據可見性,

也有了鎖的同步機制,使變量或代碼塊在某一時該,只能被一個線程訪問,確保共享數據的正確性。(Synchronized用於線程間的數據共享的)

多線程併發執行時,並不是所有數據都需要共享的,這些不需要共享的數據,讓每個線程去維護就OK了,ThreadLocal就是用於線程間的數據隔離的。

分享的視頻,還有思維導圖、視頻,都是乾貨的,你可以下載來看。主要分享分佈式架構、高可擴展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分佈式項目實戰學習架構師視頻。

獲取方式:私信關注我免費獲取!!!

深入理解java:線程本地變量 java.lang.ThreadLocal類

 

深入解析ThreadLocal類:

先我們來看一下ThreadLocal類是如何爲每個線程創建一個變量的副本的。

先看下get方法的實現:

深入理解java:線程本地變量 java.lang.ThreadLocal類

 

第一句是取得當前線程,然後通過getMap(t)方法獲取到一個map,map的類型爲ThreadLocalMap。

然後接着下面獲取到Entry鍵值對,注意這裏獲取Entry時參數傳進去的是 this,即ThreadLocal實例,而不是當前線程t。如果獲取成功,則返回value值。

如果map爲空,則調用setInitialValue方法返回value。

接着看一下getMap方法中做了什麼:

深入理解java:線程本地變量 java.lang.ThreadLocal類

 

在getMap中,是調用當期線程t,返回當前線程t中的一個成員變量threadLocals,類型爲ThreadLocalMap。

這裏意味着每一個線程都自帶一個ThreadLocalMap成員變量。

繼續取看ThreadLocalMap的實現:

深入理解java:線程本地變量 java.lang.ThreadLocal類

 

可以看到ThreadLocalMap的Entry繼承了WeakReference,並且使用ThreadLocal作爲鍵值

也就是說WeakReference封裝了ThreadLocal,並作爲了ThreadLocalMap的Entry的Key。

總結一下,在每個線程Thread內部有一個ThreadLocalMap類型的成員變量threadLocals,

這個ThreadLocalMap成員變量的Entry的Key爲,當前ThreadLocal變量的WeakReference封裝,value爲變量。

爲何ThreadLocalMap的鍵值爲ThreadLocal對象? 因爲每個線程中可能需要有多個threadLocal變量,也就是ThreadLocalMap裏面可能會有多個Entry。

在每個線程內部 第一次調用ThreadLocal.get方法時,都會返回Null。因爲默認情況下,initialValue方法返回的是null。

null 賦給(強轉) 基本數據類型時會拋的空指針,null賦給 引用類型沒問題。

可以在ThreadLocal的構造函數重寫initialValue()方法。如下

ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){

protected Long initialValue() {

return Thread.currentThread().getId();

};

};

或者在調用ThreadLocal.get方法之前,需要先執行set(),以保證threadlocals中有值。

或者value爲引用類型變量,null賦給 引用類型沒問題。,如下,hibernate中典型的ThreadLocal的應用:

深入理解java:線程本地變量 java.lang.ThreadLocal類

 

開篇說ThreadLocal創建副本 的說法是不太正確的。爲什麼?

從上面這個hibernate的例子來看,這是一個使用ThreadLocal解決數據庫連接的單例 在多線程中同時操作查詢和關閉的情況。

首先這裏面不是創建副本,而是分發新的內存地址(即,新的數據庫連接的單例的內存地址),以當前ThreadLocal爲key,value指向傳入新的數據庫連接的單例的內存地址。

從而達到單個線程獲取數據連接的線程安全而已,也就是每個線程都有一個獨立的數據庫連接的單例

假設相反情況,一個數據庫連接單例 如果在2個線程中被同時引用,2線程分別同一時間操作讀取和close,肯定會出現衝突。

所以需要減少每次new的開銷還是得使用數據庫連接池

ThreadLocal的內存泄露問題:

當使用線程池來複用線程時,一個線程使用完後並不會銷燬線程,那麼 分發的那個實例會一直綁定在這個線程上。

由於WeakReference封裝了ThreadLocal,並作爲了ThreadLocalMap的Entry的Key。如果在某些時候ThreadLocal對象被賦Null的話,弱引用會被GC收集,這樣就會導致Entry的Value對象找不到,

線程被複用後如果有調用ThreadLocal.get/set方法的話,方法裏面會去做遍歷清除 以[ThreadLocal=Null ]爲Key的Entry; 但如果一直沒調用ThreadLocal.get/set方法的話就會導致內存泄漏了。

所以一般線程用完ThreadLocal後,要調用threadLocal.remove(); 如下

深入理解java:線程本地變量 java.lang.ThreadLocal類

 

分享的視頻,還有思維導圖、視頻,都是乾貨的,你可以下載來看。主要分享分佈式架構、高可擴展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分佈式項目實戰學習架構師視頻。

獲取方式:私信關注我免費獲取!!!

深入理解java:線程本地變量 java.lang.ThreadLocal類


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章