簡介
ThreadLoal 變量定躏,線程局部變量账磺,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本痊远。這里有幾點需要注意:
- 因為每個 Thread 內(nèi)有自己的實例副本垮抗,且該副本只能由當(dāng)前 Thread 使用。這是也是 ThreadLocal 命名的由來碧聪。
- 既然每個 Thread 有自己的實例副本冒版,且其它 Thread 不可訪問,那就不存在多線程間共享的問題矾削。
- ThreadLocal 提供了線程本地的實例壤玫。它與普通變量的區(qū)別在于,每個使用該變量的線程都會初始化一個完全獨立的實例副本哼凯。ThreadLocal 變量通常被private static修飾欲间。當(dāng)一個線程結(jié)束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收断部。
總的來說猎贴,ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。
使用
private static final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
threadLocal.set();
threadLocal.get();
threadLocal.remove();
原理
類圖
//ThreadLocal是個泛型類她渴,保證可以接受任何類型的對象
public class ThreadLocal<T> {
...
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
...
}
//ThreadLocalMap 用來存放數(shù)據(jù)
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
...
}
}
...
}
通過代碼理解原理
ThreadLocal提供了線程內(nèi)存儲變量的能力达址,這些變量在每個線程之間是相互獨立的。我們可以通過get和set方法得到或設(shè)置當(dāng)前線程對應(yīng)的值趁耗。
設(shè)置數(shù)據(jù) set()
public void set(T value) {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取數(shù)據(jù)實際的存儲結(jié)構(gòu)
ThreadLocalMap map = getMap(t);
//如果map為空
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//t為當(dāng)前線程沉唠,在當(dāng)前線程中維護了一個ThreadLocalMap
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//將新創(chuàng)建的ThreadLocalMap賦值給Thread中的threadLocals變量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
獲取數(shù)據(jù) get()
public T get() {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取數(shù)據(jù)實際的存儲結(jié)構(gòu)
ThreadLocalMap map = getMap(t);
if (map != null) {
//以當(dāng)前ThreadLocal對象為key獲取對應(yīng)存儲的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
//如果還沒有初始化ThreadLocalMap,則會創(chuàng)建并將初始化的value值設(shè)置到ThreadLocalMap中
T value = initialValue();//初始化的value為 null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocalMap
ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類苛败,其存儲結(jié)構(gòu)使用的是Entry满葛,實際為一個WeakReference弱引用,使用弱引用的原因為當(dāng)ThreadLocal沒有強引用的情況下罢屈,在垃圾回收的時候key可以及時的被清理掉嘀韧。但是請注意value還是強引用,不會被清理掉缠捌,所以會出現(xiàn)key為null的value锄贷,所以在不在使用的時候及時調(diào)用remove方法清除。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
...
}
內(nèi)存泄露問題
通過上面的一些介紹可以知道曼月,ThreadLocalMap中維護了一個Entry的數(shù)據(jù)結(jié)構(gòu)谊却,其中key使用了弱引用來保證GC時回收,但是value部分仍然存在強引用無法回收導(dǎo)致泄露十嘿。
使用場景
1因惭、在進行對象跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞绩衷,打破層次間的約束。
2激率、線程間數(shù)據(jù)隔離
3咳燕、進行事務(wù)操作,用于存儲線程事務(wù)信息乒躺。(Spring聲明式事務(wù))
4招盲、數(shù)據(jù)庫連接,Session會話管理嘉冒。