ThreadLocal常用方法、使用場景及注意事項說明
1. ThreadLocal詳解
JDK1.2版本起,Java就提供了java.lang.ThreadLocal,ThreadLocal為每個使用線程都提供獨立的變量副本,可以做到線程間的數(shù)據(jù)隔離,每個線程都可以訪問各自內部的副本變量。
線程上下文ThreadLocal又稱為"線程保險箱",ThreadLocal能夠將指定的變量和當前線程進行綁定,線程之間彼此隔離,持有不同的對象實例,從而避免了數(shù)據(jù)資源的競爭。
2. ThreadLocal的使用場景
- 在進行對象跨層傳遞的時候,可以考慮ThreadLocal,避免方法多次傳遞,打破層次間的約束。
- 線程間數(shù)據(jù)隔離。
- 進行事務操作,用于儲存線程事務信息。
注意:
ThreadLocal并不是解決多線程下共享資源的一種技術,一般情況下,每一個線程的ThreadLocal存儲的都是一個全新的對象(通過new關鍵字創(chuàng)建),如果多線程的ThreadLocal存儲了一個對象引用,那么就會面臨資源競爭,數(shù)據(jù)不一致等并發(fā)問題。
3.常用方法源碼解析
3.1 initialValue方法
protected T initialValue() {
return null;
}
此方法為ThreadLocal保存的數(shù)據(jù)類型指定的一個初始化值,在ThreadLocal中默認返回null。但可以重寫initialValue()方法進行數(shù)據(jù)初始化。
如果使用的是Java8提供的Supplier函數(shù)接口更加簡化:
// withInitial()實際是創(chuàng)建了一個ThreadLocal的子類SuppliedThreadLocal,重寫initialValue() ThreadLocal<Object> threadLocal = ThreadLocal.withInitial(Object::new);
3.2 set(T value)方法
主要存儲指定數(shù)據(jù)。
public void set(T value) {
// 獲取當前線程Thread.currentThread()
Thread t = Thread.currentThread();
// 根據(jù)當前線程獲取與之關聯(lián)的ThreadLocalMap數(shù)據(jù)結構
ThreadLocalMap map = getMap(t);
if (map != null)
// 核心方法。set 遍歷整個Entry的過程,后面有詳解
map.set(this, value);
else {
// 調用createMap(),創(chuàng)建ThreadLocalMap,key為當前ThreadLocal實例,存入數(shù)據(jù)為當前value。
// ThreadLocal會創(chuàng)建一個默認長度為16Entry節(jié)點,并將k-v放入i位置(i位置計算方式和hashmap相似,
// 當前線程的hashCode&(entry默認長度-1)),并設置閾值(默認為0)為Entry默認長度的2/3。
createMap(t, value);
}
}
// set 遍歷整個Entry的過程
private void set(ThreadLocal<?> key, Object value) {
// 獲取所有的Entry
Entry[] tab = table;
int len = tab.length;
// 根據(jù)ThreadLocal對象,計算角標位置
int i = key.threadLocalHashCode & (len-1);
// 循環(huán)查找
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 找到相同的就直接覆蓋,直接返回。
if (k == key) {
e.value = value;
return;
}
// 如果ThreadLocal為null,直接驅出并使用新數(shù)據(jù)(Value)占居原來位置,
// 這個過程主要是防止內存泄漏。
if (k == null) {
// 驅除ThreadLocal為null的Entry,并放入Value,這也是內存泄漏的重點地區(qū)
replaceStaleEntry(key, value, i);
return;
}
}
// entry都為null,創(chuàng)建新的entry,已ThreadLocal為key,將存放數(shù)據(jù)為Value。
tab[i] = new Entry(key, value);
int sz = ++size;
// ThreadLoaclMapde的當前數(shù)據(jù)元素的個數(shù)和閾值比較,再次進行key為null的清理工作。
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 整理Entry,當Entry中的ThreadLocal對象為null時,通過重新計算角標位來清理
// 以前ThreadLocal。如果Entry數(shù)量大于3/4容量進行擴容
rehash();
}
3.3 get方法
get()用于返回當前線程ThreadLocal中數(shù)據(jù)備份,當前線程的數(shù)據(jù)都存在一個ThreadLocalMap的數(shù)據(jù)結構中。
public T get() {
Thread t = Thread.currentThread();
// 獲得ThreadLocalMap對象map,ThreadLocalMap是和當前Thread關聯(lián)的,
ThreadLocalMap map = getMap(t);
if (map != null) {
// 存入ThreadLocal中的數(shù)據(jù)實際上是存儲在ThreadLocalMap的Entry中。
// 而此Entry是放在一個Entry數(shù)組里面的。
// 獲取當前ThreadLocal對應的entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 直接返回當前數(shù)據(jù)
T result = (T)e.value;
return result;
}
}
// ThreadLocalMap未初始化,首先初始化
return setInitialValue();
}
// ThreadLocal的setInitialValue方法源碼
private T setInitialValue() {
// 為ThreadLocalMap指定Value的初始化值
T value = initialValue();
Thread t = Thread.currentThread();
// 根據(jù)本地線程Thread獲取ThreadLocalMap,一下方法與Set方法相同。
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果map存在,直接調用set()方法進行賦值。
map.set(this, value);
else
// map==null;創(chuàng)建ThreadLocalMap對象,并將Thread和value關聯(lián)起來
createMap(t, value);
return value;
}
3.4 小結
initialValue():初始化ThreadLocal中的value屬性值。set():獲取當前線程,根據(jù)當前線程從ThreadLocals中獲取ThreadLocalMap數(shù)據(jù)結構,- 如果ThreadLocalmap的數(shù)據(jù)結構沒創(chuàng)建,則創(chuàng)建ThreadLocalMap,key為當前ThreadLocal實例,存入數(shù)據(jù)為當前value。ThreadLocal會創(chuàng)建一個默認長度為16Entry節(jié)點,并將k-v放入i位置(i位置計算方式和hashmap相似,當前線程的hashCode&(entry默認長度-1)),并設置閾值(默認為0)為Entry默認長度的2/3。
- 如果ThreadLocalMap存在。就會遍歷整個Map中的Entry節(jié)點,如果entry中的key和本線程ThreadLocal相同,將數(shù)據(jù)(value)直接覆蓋,并返回。如果ThreadLoca為null,驅除ThreadLocal為null的Entry,并放入Value,這也是內存泄漏的重點地區(qū)。
get()- get()方法比較簡單。就是根據(jù)Thread獲取ThreadLocalMap。通過ThreadLocal來獲得數(shù)據(jù)value。注意的是:如果ThreadLocalMap沒有創(chuàng)建,直接進入創(chuàng)建過程。初始化ThreadLocalMap。并直接調用和set方法一樣的方法。
3.4 ThreadLocalMap數(shù)據(jù)結構
set()還是get()方法都是避免不了和ThreadLocalMap和Entry打交道。ThreadLocalMap是一個類似于HashMap的一個數(shù)據(jù)結構(沒有鏈表),僅僅用于存放線程存放在ThreadLocal中的數(shù)據(jù)備份,ThreadLocalMap的所有方法對外部都是不可見的。
ThreadLocalMap中用于存儲數(shù)據(jù)的Entry,它是一個WeakReference類型的子類,之所以設計成WeakReference是為了能夠是JVM發(fā)生gc,能夠自動回收,防止內存溢出現(xiàn)象。
4. ThreadLocal的副作用
4.1 ThreadLocal引起臟數(shù)據(jù)
線程復用會產生臟數(shù)據(jù)。
由于結程池會重用 Thread 對象 ,那么與 Thread 綁定的類的靜態(tài)屬性 ThreadLocal 變量也會被重用。如果在實現(xiàn)的線程 run()方法體中不顯式地調用 remove() 清理與線程相關的ThreadLocal 信息,那么如果下一個線程不調用set()設置初始值,就可能 get()到重用的線程信息,包括 ThreadLocal 所關聯(lián)的線程對象的 value 值。
// java.lang.Thread#threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
4.2 ThreadLocal引起的內存泄漏
在上面提到ThreadLocalMap中存放的Entry是WeakReference的子類。所以在JVM觸發(fā)GC(young gc,F(xiàn)ull GC)時,都會導致Entry的回收
在get數(shù)據(jù)的時候,增加檢查,清除已經(jīng)被回收器回收的Entry(WeakReference可以自動回收)
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
ThreadLocal<?> k = e.get();
...
if (k == null)
// 清除 key 是 null 的Entry
expungeStaleEntry(i);
...
return null;
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
// 清除key==null 的Entry
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
set數(shù)據(jù)時增加檢查,刪除已經(jīng)被垃圾回收器清理的Entry,并將其移除
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
// 清除key==null 的Entry
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
基于上面三點:ThreadLocal在一定程度上保證不會發(fā)生內存泄漏。但是Thread類中有ThreadlocalMap的引用,導致對象的可達性,故不能回收。
ThreadLocal被置為null清除了。但是通過ThreadLocalMap還是被Thread類引用。導致該數(shù)據(jù)是可達的。所以內存得不到釋放,除非當前線程結束,Thread引用就會被垃圾回收器回收。如圖所示

5
5. ThreadLocal內存泄漏解決方案及remove方法源碼解析
解決ThreadLocal內存泄漏的常用方法是:在使用完ThreadLocal之后,及時remove掉。
public void remove() {
// 根據(jù)當前線程,獲取ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// map不為null,執(zhí)行remove操作
m.remove(this);
}
// ThreadLocal 的remove()
private void remove(ThreadLocal<?> key) {
// 獲取存放key-value的數(shù)組。
Entry[] tab = table;
int len = tab.length;
// 根據(jù)ThreadLocal的HashCode確定唯一的角標
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
// 如果和本ThreadLocal相同。將引用置null。
e.clear();
// 實行Enty和Entry.value置null。源碼中 tab[staleSlot].value = null; tab[staleSlot] = null;
expungeStaleEntry(i);
return;
}
}
}
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
舉例講解Java的Jackson庫中ObjectMapper類的使用
這篇文章主要介紹了舉例講解Java的Jackson庫中ObjectMapper類的使用,Jackson庫通常被用來實現(xiàn)Java的對象和JSON之間的轉換功能,需要的朋友可以參考下2016-01-01
Mybatis的mapper標簽 namespace屬性用法說明
這篇文章主要介紹了Mybatis的mapper標簽 namespace屬性用法說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Java動態(tài)數(shù)組Arraylist存放自定義數(shù)據(jù)類型方式
這篇文章主要介紹了Java動態(tài)數(shù)組Arraylist存放自定義數(shù)據(jù)類型方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
基于Redisson實現(xiàn)注解式分布式鎖的示例代碼
這篇文章主要為大家詳細介紹了如何基于Redisson實現(xiàn)注解式分布式鎖,文中的示例代碼講解詳細,具有一定的參考價值,需要的可以了解一下2023-07-07
Mybatis?Plus?中的LambdaQueryWrapper示例詳解
這篇文章主要介紹了Mybatis?Plus?中的LambdaQueryWrapper,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
如何使用IDEA2022.1?創(chuàng)建Spring?Boot項目
這篇文章主要介紹了如何使用IDEA2022.1?創(chuàng)建Spring?Boot項目,大家在使用idea開發(fā)工具時發(fā)現(xiàn)給以往的版本略微的不同,細心的小編在此記錄下,需要的朋友可以參考下2022-08-08

