深入理解java:執行緒本地變數 java.lang.ThreadLocal類

java人生發表於2018-12-10

hreadLocal,很多人都叫它做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。

可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了一個副本,那樣每個執行緒可以訪問自己內部的副本變數。

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

多執行緒併發執行時,需要資料共享,因此才有了volatile變數解決 多執行緒間的資料可見性,

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

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

給大家推薦一個程式設計師學習秋秋群:945622618。群裡有分享的視訊,還有思維導圖
群公告有視訊,都是乾貨的,你可以下載來看。主要分享分散式架構、高可擴充套件、高效能、高併發、效能優化、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類

給大家推薦一個程式設計師學習秋秋群:945622618。群裡有分享的視訊,還有思維導圖
群公告有視訊,都是乾貨的,你可以下載來看。主要分享分散式架構、高可擴充套件、高效能、高併發、效能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分散式專案實戰學習架構師視訊。

深入理解java:執行緒本地變數 java.lang.ThreadLocal類


相關文章