ThreadLocal是什么却音?
顧名思義,ThreadLocal可以為每個線程獨立存儲不同的值(線程本地變量)。其意義在于高并發(fā)場景下變量被多個線程訪問互不影響算灸,有效避免線程安全問題和同步帶來的性能開銷忿族。當然它也存在一定缺陷锣笨,由于每個線程都會創(chuàng)建ThreadLocal變量,就會帶來一定的內存消耗道批;它的思想就是“以空間換時間”错英。
特殊情況:InhertiableThreadLocal并不是只存儲當前線程的值,它默認會集成父類中的值隆豹。
ThreadLocal類方法定義
public class ThreadLocal<T> {
public T get();
private T setInitialValue();
public void set(T value);
public void remove();
}
get:用于獲取當前線程私有的ThreadLocal變量椭岩;
setInitialValue:可以進行重寫設置當前ThreadLocal對應的數據,用于第一次調用get時懶加載獲仍胍痢簿煌;
set:設置當前Thread的ThreadLocal值;
remove:刪除ThreadLocal中的數據鉴吹;
get方法具體實現(xiàn)
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
* @return the current thread's value of this thread-local
*/
public T get() {
//獲取當前線程
Thread t = Thread.currentThread();
//獲取Thread類中定義的ThreadLocal.ThreadLocalMap變量
ThreadLocalMap map = getMap(t);
//判斷map是否為null
if (map != null) {
//獲取當前ThreadLocal對應的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//獲取set的值
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
//當map為null或未設置值時調用
return setInitialValue();
}
其中ThreadLocalMap是ThreadLocal中的靜態(tài)內部類姨伟,內部維護了一個Entry數組的table,可以看作是一個kv的map豆励,只不過key是當前ThreadLocal生成的Hash值夺荒;
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
setInitialValue具體實現(xiàn)
當get方法中map為null或未設置值時會調用setInitialValue方法,接下來看看它的實現(xiàn):
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
* @return the initial value
*/
private T setInitialValue() {
//value等于重寫initialValue后返回的值良蒸,否則默認返回null
T value = initialValue();
//獲取當前線程
Thread t = Thread.currentThread();
//獲取當前Thread類中定義的ThreadLocal.ThreadLocalMap變量
ThreadLocalMap map = getMap(t);
//map為null直接添加到table中技扼,否則新建map
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
值得注意的是其中initialValue默認是返回null的,當然你也可以選擇重寫來懶加載數據嫩痰,當調用get方法的時候才獲取剿吻,就像這樣:
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
int i = 1;
@Override
protected Integer initialValue() {
return i;
}
};
其中當map為null是會調用createMap方法,將創(chuàng)建的ThreadLocalMap賦值到當前Thread的threadLocals變量串纺,完成關聯(lián)丽旅!
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
* @param t
* the current thread
* @param firstValue
* value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
set方法具體實現(xiàn)
和上面setInitialValue中實現(xiàn)幾乎一樣椰棘,這里就不贅述了...
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
* @param value
* the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
//獲取當前Thread
Thread t = Thread.currentThread();
//獲取當前Thread類中定義的ThreadLocal.ThreadLocalMap變量
ThreadLocalMap map = getMap(t);
//map為null直接添加到table中,否則新建map
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove方法具體實現(xiàn)
獲取當前線程中的ThreadLocal.ThreadLocalMap榄笙,清除table數組中以當前ThreadLocal Hash為key的數據邪狞。
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
* @since 1.5
*/
public void remove() {
//獲取當前Thread類中定義的ThreadLocal.ThreadLocalMap變量
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//刪除以當前ThreadLocal為key設置的數據
m.remove(this);
}
關于內存泄露的問題
拋出問題
- 首先ThreadLocal實例被線程的ThreadLocalMap實例持有,也可以看成被線程持有茅撞。
- 如果應用使用了線程池帆卓,那么之前的線程實例處理完之后出于復用的目的依然存活
所以,ThreadLocal設定的值被持有米丘,導致內存泄露剑令。
結論
首先Entry是繼承WeakReference<ThreadLocal<?>>的,也就是弱引用拄查,ThreadLocalMap的key使用的是ThreadLocal的弱引用尚洽,所以并不會導致內存泄露,關于java中的四種引用我會在后面的文章中記錄靶累。
最后
第一次寫源碼分析腺毫,有不好的地方歡迎指正。
參考資料
https://blog.appoptics.com/introduction-to-java-threadlocal-storage/
https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/