最近有人問我ThreadLocal
是如何做到在每個執行緒中的值都是隔離的,此處寫篇文章來簡單記錄下。
Thread
自身,別的執行緒無法對其影響。(需要注意:需要呼叫ThreadLocal的remove方法)ThreadLocal
型別的變數只有自身的執行緒可以存取,所以這點是成立的。)比如:
使用者登入成功後,需要將登入使用者資訊
儲存起來,以方便在系統中的任何地方都可以使用到,那麼此時就可以使用ThreadLocal
來實現。例如:Spring Security
中的ThreadLocalSecurityContextHolderStrategy
類。
private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();
ThreadLocal的範例推薦使用private static final
來修飾。
ThreadLocal
: 此類提供了一個簡單的set
,get
,remove
方法,用於設定,獲取或移除 繫結到執行緒本地變數中的值。
ThreadLocalMap
: 這是在ThreadLocal中定義的一個類,可以簡單的將它理解成一個Map,不過它的key是WeakReference弱參照型別,這樣當這個值沒有在別的地方參照時,在發生垃圾回收時,這個map的key
會被自動回收,不過它的值不會被自動回收。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
// key 弱參照
super(k);
// 值強參照
value = v;
}
}
Thread
:這個是執行緒類,在這個類中存在一個threadLocals
變數,具體的型別是ThreadLocal.ThreadLocalMap
。
public void set(T value) {
// 獲取當前執行緒
Thread t = Thread.currentThread();
// 獲取繫結到這個執行緒自身的 ThreadLocalMap,這個ThreadLocalMap是從Thread類的`threadLocals`變數中獲取的
ThreadLocalMap map = getMap(t);
if (map != null) {
// 向map中設定值,key為 ThreadLocal 物件的範例。
map.set(this, value);
} else {
// 如果map不存在,則建立出來。
createMap(t, value);
}
}
通過上方的程式碼,我們可知: 當我們向ThreadLocal中設定一個值,會經過如下幾個步驟:
Thread
ThreadLocalMap
物件。ThreadLocalMap
中設定值,key為ThreadLocal
物件,值為具體的值。public T get() {
// 獲取當前執行緒
Thread t = Thread.currentThread();
// 獲取這個執行緒自身繫結的 ThreadLocalMap 物件
ThreadLocalMap map = getMap(t);
if (map != null) {
// this是ThreadLocal物件,獲取Map中的Entry物件
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 獲取具體的值
T result = (T)e.value;
return result;
}
}
// 設定初始值
return setInitialValue();
}
從上方的get 和 set 方法可以得知,通過往ThreadLocal物件中設定值或獲取值,其實是最終操作到Thread物件中的threadLocals欄位中,而這個欄位是Thread自身的,因此做到了隔離。
private final int threadLocalHashCode = nextHashCode();
// 該 ThreadLocal 物件自身的hash code值
private final int threadLocalHashCode = nextHashCode();
// 從0開始
private static AtomicInteger nextHashCode = new AtomicInteger();
// 每次遞增固定的值
private static final int HASH_INCREMENT = 0x61c88647;
// hash code 值計算
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
從上方的程式碼中可以,ThreadLocal
類在範例化出來之後,它的hash code值(threadLocalHashCode)
就是固定的,即使ThreadLocal
呼叫了set
方法,設定了別的值,它的hash code值
也不會發生變化。
此欄位threadLocalHashCode
為ThreadLocal
物件的hash值,在ThreadLocalMap
中需要用到這個hash值。
ThreadLocalMap
解決hash衝突的辦法很簡單。就是通過線性探測法。如果發生了衝突,就去找陣列後面的可用位置。具體看上圖。演示的是A和B 2個ThreadLocal物件,然後發生了衝突,A和B存在的位置在那個地方。
ThreadLocal為什麼會存在記憶體漏失呢?
這是因為ThreadLocalMap
中的key
是WeakReference
型別,也就是弱參照型別,而弱參照型別的資料在沒有外部強參照型別的話,在發生gc
的時候,會自動被回收掉。注意:
此時是key
被回收了,但是value
是沒有回收的。因此在ThreadLocalMap
中的Entry[]
中可能存在key
是null
,但是value
是具體的值的物件,因此就發生了記憶體漏失。
解決記憶體漏失:
當我們使用完ThreadLocal
物件後,需要在適當的時機呼叫ThreadLocal#remove()
方法。 否則就只有等Thread
自動退出才能清除,如果是使用了執行緒池,Thread
會重用,清除的機會就更難。
本文來自部落格園,作者:huan1993,轉載請註明原文連結:https://www.cnblogs.com/huan1993/p/16418652.html