在Java中,對象是線程共享的,當我們想讓線程獨自擁有專屬於自己的變量時,可以使用ThreadLocal類。
ThreadLocal類提供了線程局部(Thread-Locak)變量。這些變量不同於他們的普通對應物,因爲訪問某個變量的每個線程都有自己的局部變量。當使用ThreadLocal維護變量時,ThreadLocal爲每個使用該變量的線程提供獨立的變量副本。所以,每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。
從線程的角度看,目標變量就是線程的本地變量,這也是Local所代表的含義。
一、使用方法
- public T get():返回此線程局部變量的當前線程副本中的值。如果變量沒有用於當前線程的值,則先將其初始化爲調用 initalValue() 方法返回的值。
- protected T initialValue():返回此線程局部變量的當前線程的”初始值“。線程第一次使用 get() 方法會調用此方法。但如果線程之前調用了 set() 方法,則不會對該線程再調用 initialValue() 方法。通常,此方法對每個線程最多調用一次,但如果在調用 get() 後又使用 remove() 方法,則可能再次調用此方法。
- public void remove():移除此線程局部變量當前線程的值。如果此線程局部變量隨後被當前線程讀取,且這期間線程沒有設置其值,則將調用其 initialValue() 方法重新初始化其值。這將導致在當前線程多次調用 initialValue() 方法。
- public void set(T value):將此線程局部變量的當前線程副本中的值設置爲指定值。大部分子類不需要重寫此方法,它們只依靠 initialValue() 方法來設置線程局部變量的值。
二、ThreadLocal實現原理
ThreadLocal.ThreadLocalMap threadLocals = null; //Thread對象中含有字段 ThreadLocalMap
get方法
get方法返回當前線程中,存有的ThreadLocal局部變量。如果當前線程中沒有存入該值,將調用initialValue方法返回的值。源碼如下:
public T get() {
Thread t = Thread.currentThread(); //當前線程
ThreadLocalMap map = getMap(t); //獲取該線程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //獲取map中鍵爲該ThreadLocal的entry
if (e != null) { //若有值 則返回該值
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //若沒有該值 則調用initialValue方法
}
getMap(Thread t) 方法用來獲取線程中的ThreadLocalMap:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //獲取線程t的局部變量表
}
setInitialValue()方法將獲取初始值,並往線程的局部變量表中填入threadLocal-Value 鍵值對。如果還沒有局部變量表,將爲該線程創建。
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;
}
set方法
set方法將往線程的局部變量表中填入變量副本的鍵值對。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //獲取局部變量表
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove方法
remove方法將清除線程局部變量表中的該threadLocal鍵值對。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
以上幾個方法的核心都是對線程的ThreadLocalMap進行操作,下面我們將查看ThreadLocalMap的源碼。
三、ThreadLocalMap實現原理
static class ThreadLocalMap {
//map中的每個節點Entry,其鍵key是ThreadLocal並且還是弱引用,這也導致了後續會產生內存泄漏問題的原因。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
/**
* 初始化容量爲16,以爲對其擴充也必須是2的指數
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 真正用於存儲線程的每個ThreadLocal的數組,將ThreadLocal和其對應的值包裝爲一個Entry。
*/
private Entry[] table;
///....其他的方法和操作都和map的類似
}
總之,爲不同線程創建不同的ThreadLocalMap,用線程本身爲區分點,每個線程之間其實沒有任何的聯繫,說是說存放了變量的副本,其實可以理解爲爲每個線程單獨new了一個對象。
四、內存泄漏問題
網上大家討論說ThreadLocal會導致內存泄漏,原因如下:
- 首先ThreadLocal實例被線程的ThreadLocalMap實例持有,也可以看成被線程持有。
- 如果應用使用了線程池,那麼之前的線程實例處理完之後出於複用的目的依然存活
- 所以,ThreadLocal設定的值被持有,導致內存泄露。