簡介
從名稱看,ThreadLocal 也就是thread和local的組合,也就是一個thread有一個local的變量副本
ThreadLocal提供了線程的本地副本,也就是說每一個線程將會擁有一個本身獨立的變量副本
方法簡潔幹練,類信息以及方法列表以下
示例
在測試類中定義了一個ThreadLocal變量,用於保存String類型數據
建立了兩個線程,分別設置值,讀取值,移除後再次讀取
package test2;
/**
* Created by noteless on 2019/1/30. Description:
*/
public class T21 {
//定義ThreadLocal變量
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
//thread1中設置值
threadLocal.set("this is thread1's local");
//獲取值
System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get());
//移除值
threadLocal.remove();
//再次獲取
System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get());
}, "thread1");
Thread thread2 = new Thread(() -> {
//thread2中設置值
threadLocal.set("this is thread2's local");
//獲取值
System.out.println(Thread.currentThread().getName()+": threadLocal value:" + threadLocal.get());
//移除值
threadLocal.remove();
//再次獲取
System.out.println(Thread.currentThread().getName()+": after remove threadLocal value:" + threadLocal.get());
}, "thread2");
thread1.start();
thread2.start();
}
}
執行結果
從結果能夠看獲得,每一個線程中能夠有本身獨有的一份數據,互相沒有影響
remove以後,數據被清空
從上面示例也能夠看出來一個狀況:
若是兩個線程同時對一個變量進行操做,互相之間是沒有影響的,換句話說,這很顯然並非用來解決共享變量的一些併發問題,好比多線程的協做
由於ThreadLocal的設計理念就是共享變私有,都已經私有了,還談啥共享?
好比以前的消息隊列,生產者消費者的示例中
final LinkedList<Message> messageQueue = new LinkedList<>();
若是這個LinkedList是ThreadLocal的,生產者使用一個,消費者使用一個,還協做什麼呢?
可是共享變私有,如同併發變串行,或許適合解決一些場景的線程安全問題,由於看起來就如同沒有共享變量了,不共享即安全,可是他並非爲了解決線程安全問題而存在的
實現分析
在Thread中有一個threadLocals變量,類型爲ThreadLocal.ThreadLocalMap
而ThreadLocalMap則是ThreadLocal的靜態內部類,他是一個設計用來保存thread local 變量的自定義的hash map
全部的操做方法都是私有的,也就是不對外暴露任何操做方法,也就是隻能在ThreadLocal中使用了
此處咱們不深刻,就簡單理解爲是一個hash map,用於保存鍵值對
也就是說Thread中有一個「hashMap」能夠用來保存鍵值對
set方法
看一下ThreadLocal的set方法
在這個方法中,接受參數,類型爲T的value
首先獲取當前線程,而後調用getMap(t)
這個方法也很簡單,就是直接返回Thread內部的那個「hashMap」(threadLocals是默認的訪問權限)
繼續回到set方法,若是這個map不爲空,那麼以this爲key,value爲值,也就是ThreadLocal變量做爲key
若是map爲空,那麼進行給這個線程建立一個map ,而且將第一組值設置進去,key仍舊是這個ThreadLocal變量
簡言之:
調用一個ThreadLocal的set方法,會將:以這個ThreadLocal類型的變量爲key,參數爲value的這一個鍵值對,保存在Thread內部的一個「hashMap」中
get方法
在get方法內部仍舊是獲取當前線程的內部的這個「hashMap」,而後以當前對象this(ThreadLocal)做爲key,進行值的獲取
咱們對這兩個方法換一個思路理解:
每一個線程可能運行過程當中,可能會操做不少的ThreadLocal變量,怎麼區分各自?
直觀的理解就是,咱們想要獲取某個線程的某個ThreadLocal變量的值
一個很好的解決方法就是藉助於Map進行保存,ThreadLocal變量做爲key,local值做爲value
假設這個map名爲:threadLocalsMap,能夠提供setter和getter方法進行設置和讀取,內部爲
- threadLocalsMap.set(ThreadLocal key,T value)
- threadLocalsMap.get(ThreadLocal key)
這樣就是能夠達到thread --- local的效果,可是是否存在一些使用不便?咱們內部定義的是ThreadLocal變量,可是隻是用來做爲key的?是否直接經過ThreadLocal進行值的獲取更加方便呢?
怎麼可以作到數據讀取的倒置?由於畢竟值的確是保存在Thread中的
其實也很簡單,只須要內部進行轉換就行了,對於下面兩個方法,咱們都須要 ThreadLocal key
threadLocalsMap.set(ThreadLocal key,T value)
threadLocalsMap.get(ThreadLocal key)
而這個key不就是這個ThreadLocal,不就是this 麼
因此:
- ThreadLocal.set(T value)就內部調用threadLocalsMap.set(this,T value)
- ThreadLocal.get()就內部調用threadLocalsMap.get(this)
因此總結下就是:
- 每一個Thread內部保存了一個"hashMap",key爲ThreadLocal,這個線程操做了多少個ThreadLocal,就有多少個key
- 你想獲取一個ThreadLocal變量的值,就是ThreadLocal.get(),內部就是hashMap.get(this);
- 你想設置一個ThreadLocal變量的值,就是ThreadLocal.set(T value),內部就是hashMap.set(this,value);
關鍵只是在於內部的這個「hashMap」,ThreadLocal只是讀寫倒置的「殼」,能夠更簡潔易用的經過這個殼進行變量的讀寫
「倒置」的紐帶,就是getMap(t)方法
remove方法
remove方法也是簡單,當前線程,獲取當前線程的hashMap,remove
初始值
再次回頭看下get方法,若是第一次調用時,指定線程並無threadLocals,或者根本都沒有進行過set
會發生什麼?
以下圖所示,會調用setInitialValue方法
在setInitialValue方法中,會調用initialValue方法獲取初始值,若是該線程沒有threadLocals那麼會建立,若是有,會使用這個初始值構造這個ThreadLocal的鍵值對
簡單說,若是沒有set過(或者壓根內部的這個threadLocals就是null的),那麼她返回的值就是初始值
這個內部的initialValue方法默認的返回null,因此一個ThreadLocal若是沒有進行set操做,那麼初始值爲null
如何進行初始值的設定?
能夠看得出來,這是一個protected方法,因此返回一個覆蓋了這個方法的子類不就行了?在子類中實現初始值的設置
在ThreadLocal中提供了一個內部類SuppliedThreadLocal,這個內部類接受一個函數式接口Supplier做爲參數,經過Supplier的get方法獲取初始值
Supplier是一個典型的內置函數式接口,無入參,返回類型T,既然是函數式接口也就是能夠直接使用Lambda表達式構造初始值了!!!
如何構造這個內部類,而後進而進行初始化參數的設置呢?
提供了withInitial方法,這個方法的參數就是Supplier類型,能夠看到,這個方法將入參,透傳給SuppliedThreadLocal的構造方法,直接返回一個SuppliedThreadLocal
換句話說,咱們不是但願可以藉助於ThreadLocal的子類,覆蓋initialValue()方法,提供初始值嗎?
這個withInitial就是可以達成目標的一個方法!
使用withInitial方法,建立具備初始值的ThreadLocal類型的變量,從結果能夠看得出來,咱們沒有任何的設置,能夠獲取到值
稍做改動,增長了一次set和remove,從打印結果看得出來,set後,使用的值就是咱們新設置的
而一旦remove以後,那麼仍舊會使用初始值
注意:
對於initialValue方法的覆蓋,其實即便沒有提供這個子類以及這個方法也都是能夠的,由於本質是要返回一個子類,而且覆蓋了這個方法
咱們能夠本身作,也能夠直接匿名類,以下所示:建立了一個ThreadLocal的子類,覆蓋了initialValue方法
ThreadLocal <類型 > threadLocalHolder =new ThreadLocal <類型> () { html
public 類型 initialValue() { 瀏覽器
return XXX; 安全
} session
};
多線程
可是很顯然,提供了子類和方法以後,咱們就能夠藉助於Lambda表達式進行操做,更加簡介
總結:
經過set方法能夠進行值的設定
經過get方法能夠進行值的讀取,若是沒有進行過設置,那麼將會返回null;若是使用了withInitial方法提供了初始值,將會返回初始值
經過remove方法將會移除對該值的寫入,再次調用get方法,若是使用了withInitial方法提供了初始值,將會返回初始值,不然返回null
對於get方法,很顯然若是沒有提供初始值,返回值爲null,在使用時是須要注意不要引發NPE異常
ThreadLocal,thread local,每一個線程一份,究竟是什麼意思?
他的意思是對於一個ThreadLocal類型變量,每一個線程有一個對應的值,這個值的名字就是ThreadLocal類型變量的名字,值是咱們set進去的變量
可是若是set設置的是共享變量,那麼ThreadLocal其實本質上仍是同一個對象不是麼?
這句話若是有疑問的話,能夠這麼理解
對於同一個ThreadLocal變量a,每一個線程有一個map,map中都有一個鍵值對,key爲a,值爲你保存的值
可是這個值,到底每一個線程都是全新的?仍是使用的同一個?這是你本身的問題了!!!
ThreadLocal能夠作到每一個線程有一個獨立的一份值,可是你非得使用共享變量將他們設置成一個,那ThreadLocal是不會保障的
這就比如一個對象,有不少引用指向他,每一個線程有一個獨立的引用,可是對象根本仍是隻有一個
因此,從這個角度更容易理解,爲何說ThreadLocal並非爲了解決線程安全問題而設計的,由於他並不會爲線程安全作什麼保障,他的能力是持有多個引用,這多個引用是否能保障是多個不一樣的對象,你來決策
因此咱們最開始說的,ThreadLocal會爲每一個線程建立一個變量副本的說法是不嚴謹的
是他有這個能力作到這件事情,可是究竟是什麼對象,仍是要看你set的是什麼,set自己不會對你的值進行干涉
不過咱們一般就是在合適的場景下經過new對象建立,該對象在線程內使用,也不須要被別的線程訪問
以下圖所示,你放進去的是一個共享變量,他們就是同一個對象
應用場景
前面說過,對於以前生產者消費者的示例中,就不適合使用ThreadLocal,由於問題模型就是要多線程之間協做,而不是爲了線程安全就將共享變量私有化
好比,銀行帳戶的存款和取款,若是藉助於ThreadLocal建立了兩個帳戶對象,就會有問題的,初始值500,明明又存進來1000塊,可支配的總額仍是500
那ThreadLocal適合什麼場景呢?
既然是每一個線程一個,天然是適合那種但願每一個線程擁有一個的那種場景(好像是廢話)
一個線程中一個,也就是線程隔離,既然是一個線程一個,那麼同一個線程中調用的方法也就是共享了,因此說,有時,ThreadLocal會被用來做爲參數傳遞的工具
由於它可以保障同一個線程中的值是惟一的,那麼他就共享於全部的方法中,對於全部的方法來講,至關於一個全局變量了!
因此能夠用來同一個線程內全局參數傳遞
不過要慎用,由於「全局變量」的使用對於維護性、易讀性都是挑戰,尤爲是ThreadLocal這種線程隔離,可是方法共享的「全局變量」
如何保障必然是獨立的一個私有變量?
對於ThreadLocal無初始化設置的變量,返回值爲null
因此能夠進行判斷,若是返回值爲null,能夠進行對象的建立,這樣就能夠保障每一個線程有一個獨立的,惟一的,特有的變量
示例
對於JavaWeb項目,你們都瞭解過Session
ps:此處不對session展開介紹,打開瀏覽器輸入網址,這就會創建一個session,關閉瀏覽器,session就失效了
在這一個時間段內,一個用戶的多個請求中,共享同一個session
Session 保存了不少信息,有的須要經過 Session 獲取信息,有些又須要修改 Session 的信息
每一個線程須要獨立的session,並且不少地方都須要操做 Session,存在多方法共享 Session 的需求,因此session對象須要在多個方法中共享
若是不使用 ThreadLocal,能夠在每一個線程內建立一個 Session對象,而後在多個方法中將他做爲參數進行傳遞
很顯然,若是每次都顯式的傳遞參數,繁瑣易錯
這種場景就適合使用ThreadLocal
下面的示例就模擬了多方法共享同一個session,可是線程間session隔離的示例
public class T24 {
/**
* session變量定義
*/
static ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<>();
/**
* 獲取session
*/
static Session getSession() {
if (null == sessionThreadLocal.get()) {
sessionThreadLocal.set(new Session());
}
return sessionThreadLocal.get();
}
/**
* 移除session
*/
static void closeSession() {
sessionThreadLocal.remove();
}
/**
* 模擬一個調用session的方法
*/
static void fun1(Session session) {
}
/**
* 模擬一個調用session的方法
*/
static void fun2(Session session) {
}
public static void main(String[] args) {
new Thread(() -> {
fun1(getSession());
fun2(getSession());
closeSession();
}).start();
}
/**
* 模擬一個session
*/
static class Session {
}
}
因此,ThreadLocal最根本的使用場景應該是: 併發
在每一個線程但願有一個獨有的變量時(這個變量還極可能須要在同一個線程內共享)
避免每一個線程還須要主動地去建立這個對象(若是還須要共享,也一併解決了參數來回傳遞的問題)
換句話說就是,「如何優雅的解決:線程間隔離與線程內共享的問題」,而不是說用來解決亂七八糟的線程安全問題
因此說若是有些場景你須要線程隔離,那麼考慮ThreadLocal,而不是你有了什麼線程安全問題須要解決,而後求助於ThreadLocal,這不是一回事
既然可以線程內共享,天然的確是能夠用來線程內全局傳參,可是不要濫用
再次注意:
ThreadLocal只是具備這樣的能力,是你可以作到每一個線程一個獨有變量,可是若是你set時,不是傳遞的new出來的新變量,也就只是理解成「每一個線程不一樣的引用」,對象仍是那個對象(有點像參數傳遞時的值傳遞,對於對象傳遞的就是引用)
內存泄漏
ThreadLocal很好地解決了線程數據隔離的問題,可是很顯然,也引入了另外一個空間問題
若是線程數量不少,若是ThreadLocal類型的變量不少,將會佔用很是大的空間
而對於ThreadLocal自己來講,他只是做爲key,數據並不會存儲在它的內部,因此對於ThreadLocal
ThreadLocalMap內部的這個Entity的key是弱引用
以下圖所示,實線表示強引用,虛線表示弱引用
對於真實的值是保存在Thread裏面的ThreadLocal.ThreadLocalMap threadLocals中的
藉助於內部的這個map,經過「殼」ThreadLocal變量的get,能夠獲取到這個map的真正的值,也就是說,當前線程中持有對真實值value的強引用
而對於ThreadLocal變量自己,以下代碼所示,棧中的變量與堆空間中的這個對象,也是強引用的
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
不過對於Entity來講,key是弱引用
當一系列的執行結束以後,ThreadLocal的強引用也會消亡,也就是堆與棧之間的從ThreadLocal Ref到ThreadLocal的箭頭會斷開
因爲Entity中,對於key是弱引用,因此ThreadLocal變量會被回收(GC時會回收弱引用)
而對於線程來講,若是遲遲不結束,那麼就會一直存在:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value的強引用,因此value遲遲得不到回收,就會可能致使內存泄漏
ThreadLocalMap的設計中已經考慮到這種狀況,因此ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap裏全部key爲null的value
以get方法爲例
一旦將value設置爲null以後,就斬斷了引用於真實內存之間的引用,就可以真正的釋放空間,防止內存泄漏
可是這只是一種被動的方式,若是這些方法都沒有被調用怎麼辦?
並且如今對於多線程來講,都是使用線程池,那個線程極可能是與應用程序共生死的,怎麼辦?
那你就每次使用完ThreadLocal變量以後,執行remove方法!!!!
從以上分析也看得出來,因爲ThreadLocalMap的生命週期跟Thread同樣長,因此極可能致使內存泄漏,弱引用是至關於增長了一種防禦手段
經過key的弱引用,以及remove方法等內置邏輯,經過合理的處理,減小了內存泄漏的可能,若是不規範,就仍舊會致使內存泄漏
總結
ThreadLocal能夠用來優雅的解決線程間隔離的對象,必須主動建立的問題,藉助於ThreadLocal無需在線程中顯式的建立對象,解決方案很優雅
ThreadLocal中的set方法並不會保障的確是每一個線程會得到不一樣的對象,你須要對邏輯進行必定的處理(好比上面的示例中的getSession方法,若是ThreadLocal 變量的get爲null,那麼new對象)
是否真的可以作到線程隔離,還要看你本身的編碼實現,不過若是是共享變量,你還放到ThreadLocal中幹嗎?
因此一般都是線程獨有的對象,經過new建立