什么是ThreadLocal
ThreadLoacl提供了可以線程封閉的變量存儲,提供線程內(nèi)的局部變量,可以保證在多個線程并發(fā)訪問/修改同一變量時的線程安全覆获。
使用方法:
ThreadLocal<Integer> t = new ThreadLocal();
t.set(new Integer(0));
Integer i = t.get();
為什么ThreadLocal可以保證線程安全衣吠,我們看一下源碼。
public void set(T value) {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取線程內(nèi)部的Map
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//ThreadLocalMap.set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//計算哈希和在數(shù)組中的下標(biāo)
int i = key.threadLocalHashCode & (len-1);
//從數(shù)組下標(biāo)處開始下硕,向后遍歷
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key地址相同,說明是同一個ThreadLocal
if (k == key) {
e.value = value;
return;
}
//如果entry的key==null汁胆,說明弱引用被回收了梭姓,但是value依然存在,直接覆蓋
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//走到這一步嫩码,說明沒找到之前老的entry誉尖,直接新建
tab[i] = new Entry(key, value);
int sz = ++size;
//處理key被回收的entry,避免內(nèi)存泄漏铸题,并計算是否需要擴(kuò)容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
public T get() {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//獲取線程內(nèi)部的Map
ThreadLocalMap map = getMap(t);
if (map != null) {
//以當(dāng)前的ThreadLocal作為key铡恕,查找Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//否則返回初始化值,創(chuàng)建ThreadLocalMap默認(rèn)null
return setInitialValue();
}
//從Thread中獲取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
private Entry getEntry(ThreadLocal<?> key) {
//對key進(jìn)行hash計算
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
//擴(kuò)容方法丢间,擴(kuò)容為之前容量的2倍
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
看下ThreadLocalMap中的Entry源碼:
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;
}
}
}
可以看到Entry繼承了弱引用探熔,當(dāng)ThreadLocal沒有被引用時,垃圾回收會將線程中ThreadLocalMap中的key(即為ThreadLocal)回收掉烘挫。
總結(jié):
- ThreadLocal提供線程內(nèi)部的局部變量诀艰,保證線程安全
- ThreadLocal底層實現(xiàn)是在當(dāng)前線程中有個ThreadLocalMap,以ThreadLocal為key放入entry中饮六,通過entry.get()和entry.value獲取key和值其垄。
- ThreadLocalMap實現(xiàn)類似HashMap,但不完全一樣卤橄。首先是根據(jù)key的hash計算在數(shù)組上的下標(biāo)绿满,在此下標(biāo)以后進(jìn)行循環(huán)查找或者插入,判斷是否是通過一個的key的方法是根據(jù)key的引用地址判斷虽风。
- entry的弱引用內(nèi)存泄漏問題棒口,調(diào)用ThreadLocal的get和put方法時會自動清除無效的entry,但是為了保證不出現(xiàn)內(nèi)存泄漏辜膝,需要在不需要使用時顯示調(diào)用ThreadLocal.remove()方法无牵。
- 獲取ThreadLocal的hash值不是調(diào)用hashCode()方法,是使用內(nèi)部的threadLocalHashCode厂抖,每個ThreadLocal對象的threadLocalHashCode值是不一樣的茎毁,實例化ThreadLocal時會生成一個hash值。