什么是ThreadLocal
ThreadLoacl提供了可以線程封閉的變量存儲(chǔ),提供線程內(nèi)的局部變量,可以保證在多個(gè)線程并發(fā)訪問(wèn)/修改同一變量時(shí)的線程安全。
使用方法:
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;
//計(jì)算哈希和在數(shù)組中的下標(biāo)
int i = key.threadLocalHashCode & (len-1);
//從數(shù)組下標(biāo)處開(kāi)始,向后遍歷
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key地址相同,說(shuō)明是同一個(gè)ThreadLocal
if (k == key) {
e.value = value;
return;
}
//如果entry的key==null,說(shuō)明弱引用被回收了,但是value依然存在,直接覆蓋
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//走到這一步,說(shuō)明沒(méi)找到之前老的entry,直接新建
tab[i] = new Entry(key, value);
int sz = ++size;
//處理key被回收的entry,避免內(nèi)存泄漏,并計(jì)算是否需要擴(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) {
//對(duì)key進(jìn)行hash計(jì)算
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沒(méi)有被引用時(shí),垃圾回收會(huì)將線程中ThreadLocalMap中的key(即為T(mén)hreadLocal)回收掉。
總結(jié):
- ThreadLocal提供線程內(nèi)部的局部變量,保證線程安全
- ThreadLocal底層實(shí)現(xiàn)是在當(dāng)前線程中有個(gè)ThreadLocalMap,以ThreadLocal為key放入entry中,通過(guò)entry.get()和entry.value獲取key和值。
- ThreadLocalMap實(shí)現(xiàn)類似HashMap,但不完全一樣。首先是根據(jù)key的hash計(jì)算在數(shù)組上的下標(biāo),在此下標(biāo)以后進(jìn)行循環(huán)查找或者插入,判斷是否是通過(guò)一個(gè)的key的方法是根據(jù)key的引用地址判斷。
- entry的弱引用內(nèi)存泄漏問(wèn)題,調(diào)用ThreadLocal的get和put方法時(shí)會(huì)自動(dòng)清除無(wú)效的entry,但是為了保證不出現(xiàn)內(nèi)存泄漏,需要在不需要使用時(shí)顯示調(diào)用ThreadLocal.remove()方法。
- 獲取ThreadLocal的hash值不是調(diào)用hashCode()方法,是使用內(nèi)部的threadLocalHashCode,每個(gè)ThreadLocal對(duì)象的threadLocalHashCode值是不一樣的,實(shí)例化ThreadLocal時(shí)會(huì)生成一個(gè)hash值。