ThreadLocal
概述
ThreadLocal一般稱為線程本地變量未桥,它是一種特殊的線程綁定機制颗圣,將變量與線程綁定在一起句伶,為每一個線程維護一個獨立的變量副本蚂且,每個線程的私有變量统锤。通過ThreadLocal可以將對象的可見范圍限制在同一個線程內毛俏。
一個誤區(qū)
不要將synchronized與ThreadLocal進行對比,sysnchronized是一種互斥同步機制饲窿,是為了保證在多線程環(huán)境下對于共享資源的正確訪問拧抖。而ThreadLocal是提供一個“線程級”的變量作用域(線程內的static變量),它是一種線程封閉(每個線程獨享變量)技術免绿,更直白點講唧席,ThreadLocal可以理解為將對象的作用范圍限制在一個線程上下文中,使得變量的作用域為“線程級”嘲驾。
再簡單點總結淌哟,Synchronized用于線程間的數據共享,而ThreadLocal則用于線程間的數據隔離辽故。
沒有ThreadLocal的時候徒仓,一個線程在其生命周期內,可能穿過多個層級誊垢,多個方法掉弛,如果有個對象需要在此線程周期內多次調用症见,且是跨層級的(線程內共享),通常的做法是通過參數進行傳遞殃饿;而ThreadLocal將變量綁定在線程上谋作,在一個線程周期內,無論“你身處何地”乎芳,只需通過其提供的get方法就可輕松獲取到對象遵蚜。極大地提高了對于“線程級變量”的訪問便利性。
用法:
一般把ThreadLocal變量作為單獨的public static變量
通過set(...)存入數據奈惑,通過get()獲取數據
解決get返回null問題
第一次使用get都會返回null吭净,可以繼承Threadlocal再覆蓋initialValue方法可以解決
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 1;
}
}
看看set源碼
public void set(T value) {
Thread t = Thread.currentThread() //首先獲取當前線程對象
ThreadLocalMap map = getMap(t); //獲取該線程對象的ThreadLocalMap
if (map != null)
map.set(this, value);
//如果map不為空,執(zhí)行set操作肴甸,以當前threadLocal對象為key寂殉,實際存儲對象為value進行set操作
else
createMap(t, value); //如果map為空,則為該線程創(chuàng)建ThreadLocalMap
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //線程對象持有ThreadLocalMap的引用
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
從上面代碼可以看出來原在,ThreadLocal是通過在Thread里面保存了一個ThreadLocalMap才產生了全局線程私有變量不撑,在Thread中,也確實有這么一行
ThreadLocal.ThreadLocalMap threadLocals = null;
這樣子就大概知道ThreadLocal的設計思路了:
- ThreadLocal作為變量訪問的入口
- 每個Thread含有一個ThreadLocalMap對象晤斩,這個ThreadLocalMap持有對象的引用
- ThreadLocalMap以當前的threadlocal對象為key焕檬,以真正的存儲對象為value。get時通過threadlocal實例就可以找到綁定在當前線程上的對象澳泵。
看上去似乎變?yōu)镸ap<Thread,T>這種形式會自然些实愚,一個線程對應一個存儲對象。ThreadLocal這樣設計的目的主要有兩個:
- 保證當前線程結束時相關對象能盡快被回收兔辅,把value放在了線程當中腊敲,這樣線程死亡,value隨之回收
- ThreadLocalMap中的元素會大大減少维苔,而map過大更容易造成哈希沖突而導致性能變差
get源碼
public T get() {
Thread t = Thread.currentThread(); //首先獲取當前線程
ThreadLocalMap map = getMap(t); //獲取線程的map對象
if (map != null) { //如果map不為空碰辅,以threadlocal實例為key獲取到對應Entry,然后從Entry中取出對象即可介时。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
//如果map為空没宾,也就是第一次沒有調用set直接get(或者調用過set,又調用了remove)時沸柔,為其設定初始值
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
關于ThreadLocal的使用問題
如果我們為一個線程關聯的對象是“完全獨享”的循衰,也就是每個線程擁有一整套的新的棧中的對象引用+堆中的對象,那么這種情況下是真正的線程獨享變量”褐澎,相當于一種深度拷貝会钝,每個線程自己玩自己的,對該對象做任何的操作也不會對別的線程有任何影響工三。(對于對象過大的時候迁酸,如果每個線程有一個深拷貝先鱼,開銷很大)
另一種更普遍的情況,所謂的獨享變量副本奸鬓,其實也就是每個線程都擁有一個獨立的對象引用焙畔,而堆中的對象還是線程間共享的,這種情況下全蝶,自然還是會涉及到對共享資源的訪問操作闹蒜,依然會有線程不安全的風險寺枉。所以說抑淫,ThreadLocal無法解決線程安全問題。
為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對象姥闪,因為每個線程中可有多個threadLocal變量
另外還有一個坑
當使用不當時始苇,其作用域范圍使用者可能不明確。
解釋一下:線程如果運行完筐喳,也就是run方法執(zhí)行完了催式,線程注銷,ThreadLocal被回收避归;可是荣月,還有一種更加常見的情況,線程池梳毙!線程可能不會立馬注銷哺窄,也就是ThreadLocal不會被回收,如果ThreadLocal中包裝了集合類或復雜對象账锹,那內部集合類和復雜對象鎖占用的內存可能會膨脹
使用ThreadLocal的場景
最常見的ThreadLocal使用場景為 用來解決 數據庫連接萌业、Session管理等。
以Spring為例奸柬,Spring的事務管理器通過AOP切入業(yè)務代碼生年,在進入業(yè)務代碼前,會依據相應的事務管理器提取出相應的事務對象廓奕,假如事務管理器是DataSourceTransactionManager抱婉,就會從DataSource中獲取一個連接對象,通過一定的包裝后將其保存在ThreadLocal中桌粉。而且Spring也將DataSource進行了包裝授段,重寫了當中的getConnection()方法,或者說該方法的返回將由Spring來控制番甩,這樣Spring就能讓線程內多次獲取到的Connection對象是同一個侵贵。通過將連接對象保存在線程內部,不論什么時候都能拿到缘薛,此時Spring很清楚什么時候回收這個連接窍育,也就是很清楚什么時候從ThreadLocal中刪除這個元素
再來看看InheritableThreadLocal
作用:
顯然卡睦,在原本的ThreadLocal使用上,如果父線程創(chuàng)建了子線程漱抓,父線程是沒辦法把當前擁有的ThreadLocal傳遞給子線程的表锻,為何,在get()可以看得到乞娄,
public T get() {
Thread t = Thread.currentThread(); //首先獲取當前線程
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
第一步獲取當前線程瞬逊,而當前線程為子線程,子線程有自己的ThreadLocalMap仪或,跟父線程毫無關系确镊,這個地方可以從2點佐證:1. ThreadLocal的目的是為了使得每個線程有自己的獨立私有變量
- Thread的創(chuàng)建會調用init(...),當中不會為子線程繼承ThreadLocal(這里詳細可見Thread的init()函數)
而有些時候需要使得父線程的一些變量繼承給子線程范删,這時候就出現了InheritableThreadLocal蕾域,
原理
用法跟ThreadLocal一樣,所以就只講講InheritableThreadLocal的實現到旦,直接上源碼
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
看樣子旨巷,這里的關鍵在于getMap這個地方,由于是繼承添忘,所以會使用覆蓋的方法采呐,原本的getMap(Thread t)是長這樣的
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
只是返回的map不同,InheritableThreadLocal返回的map是從父線程繼承下來的搁骑,但這是在什么時候設置的斧吐?最重點的地方到了,就是在每個Thread創(chuàng)建的時候都會調用的init()方法靶病,這里只貼出跟
private void init(ThreadGroup g, Runnable target,
String name, long stackSize, AccessControlContext acc) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
來到了ThreadLocal的方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap); //這里把parentMap給復制了一遍
}
到此
參考:
http://www.cnblogs.com/dolphin0520/p/3920407.html
https://blog.csdn.net/ni357103403/article/details/51970748