簡(jiǎn)單記錄一些看threadLocal時(shí)遇到比較有意思的點(diǎn)溢豆。
ThreadLocalMap
ThreadLocal的數(shù)據(jù), 存放在Thread的屬性ThreadLocal.ThreadLocalMap threadLocals
中
ThreadLocalMap可以看做一個(gè)簡(jiǎn)單的map<ThreadLocal, Object>, 他保存了每一個(gè)ThreadLocal和對(duì)應(yīng)的值.
如有兩個(gè)ThreadLocal
ThreadLocal<Integer> a = new ThreadLocal<>();
ThreadLocal<Integer> b = new ThreadLocal<>();
在t1線程中設(shè)置值:
a.set(1);
b.set(2);
可以想象為t1.threadLocals[a] = 1, t1.threadLocals[b]=2
對(duì)于不同的Thread, 每一個(gè)Thread中threadLocals分別保存了ThreadLocal和他們對(duì)應(yīng)的值.
如上面栗子中, 如果另一個(gè)線程t2也設(shè)置了
a.set(3);
b.set(4);
那么可以想象為t2.threadLocals[a] = 1, t2.threadLocals[b]=2
為什么不要直接在ThreadLocal中使用一個(gè)map存儲(chǔ)對(duì)應(yīng)的線程和值?
如a.map[t1] = 1, a.map[t2] = 3, b.map[t1] = 2, b.map[t2] = 4
我想, 應(yīng)該是為了方便內(nèi)存回收.
使用Thread.threadLocals方式, 如果線程結(jié)束了, 那該線程set的值(如果沒有其他引用)就可以被回收了.
如果使用ThreadLocal.map模式, 已消亡Thread set的值會(huì)一直停留在內(nèi)存中.(map.key會(huì)一直指向消亡的Thread)
當(dāng)線程退出時(shí), Thread類會(huì)進(jìn)行一些清理工作, 其中就包括ThreadLocalMap
/**
* 在線程推出前, 由系統(tǒng)回調(diào),進(jìn)行資源清理
**/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
target = null;
// 加速ThreadLocalMap清理
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
WeakReference<ThreadLocal<?>>
還有一個(gè)值得注意的點(diǎn), ThreadLocalMap中的數(shù)據(jù)是存儲(chǔ)在Entry[] table中,
Entry的定義是
static class Entry extends WeakReference<ThreadLocal<?>> {
// 這個(gè)valu就是ThreadLocal.set的值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
注意這里使用了WeakReference, 而不是Entry extends ThreadLocal<?>.
WeakReference引用表明, 一旦沒有指向 referent 的強(qiáng)引用, weak reference 在 GC 后會(huì)被自動(dòng)回收.
先看一個(gè)小栗子:
ThreadLocal<Object> local = new ThreadLocal<Object>() {
protected void finalize() throws Throwable {
System.out.println(this.toString() + " threadLocal is gc");
}
};
Object reference = new Object() {
protected void finalize() throws Throwable {
System.out.println(this.toString() + " object is gc");
}
};
local.set(reference);
// 去掉強(qiáng)引用
local = null; // 代碼1
System.gc();
System.out.println(t); // 代碼2
輸出結(jié)果
multi.ThreadLocalTest$1@1714d2a threadLocal is gc
gc finish!!
要通過debug查看Thread.threadLocals的情況.
debug到代碼1
處, 可以看到
debug到代碼2
處, 可以看到
可以看到, gc后, entry的referent被回收了, 這時(shí)ThreadLocal可以被內(nèi)存回收了, 如果使用
Entry extends ThreadLocal<?>, 那么entry.key會(huì)一直引用ThreadLocal, 導(dǎo)致ThreadLocal無法被回收.
值得注意的是,local雖然回收了阶捆, 但 reference對(duì)象 并沒有回收, 哪怕你在gc前將其設(shè)為nullreference = null;
拷肌, 因?yàn)閠hreadLocals.entity中的value值依然引用它捻悯, 這點(diǎn)可能會(huì)造成內(nèi)存泄露柬祠。想要及時(shí)回收它蘸拔, 可以如下操作
// 去掉強(qiáng)引用
reference = null;
local.set(null);
local = null;
System.gc();
輸出結(jié)果
multi.ThreadLocalTest$2@1714d2a object is gc
multi.ThreadLocalTest$1@1b6b5b4 threadLocal is gc
gc finish!!
那么除了內(nèi)存回收, 使用WeakReference還有沒有其他意義呢?
我們可以看一下ThreadLocalMap的實(shí)現(xiàn), ThreadLocalMap的數(shù)據(jù)存放在Entry[] table數(shù)組中, 通過hash算法實(shí)現(xiàn)一個(gè)類Map的數(shù)據(jù)結(jié)構(gòu),就來看一下ThreadLocalMap.set方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 計(jì)算hash值
int i = key.threadLocalHashCode & (len-1);
// 找到可以set的位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// set 值
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash(); // 擴(kuò)容
}
關(guān)鍵在查找可以set的位置上, 使用hash算法無可避免會(huì)遇到hash沖突, 常見的解決方法,
如鏈路法, 在該位置使用一個(gè)鏈路存儲(chǔ)多個(gè)沖突的值(HashMap的方法),
這里使用的是線性補(bǔ)償探測(cè)法, 發(fā)生沖突是, 使用當(dāng)前位置繼續(xù)進(jìn)行hash計(jì)算(nextIndex(i, len)方法), 直到找到一個(gè)可以使用的位置.
上面栗子中,
- 如果entry為null, 可以直接創(chuàng)建entry放置到該位置.
- 如果entry.referent(就是entry指向的ThreadLocal) == key, 可以直接替換掉value
- 如果entry.referent==null, 這就是上面提到的, referent指向的ThreadLocal沒有強(qiáng)引用了,所以referent被GC回收了, 既然這時(shí)ThreadLocal沒有引用了, 所以這里就可以考慮替換該entry了, 不用再繼續(xù)尋找合適的位置了.
所以這里使用WeakReference, 還有一個(gè)意義就是減少hash沖突.
看到這里, 不得不說ThreadLocal的實(shí)現(xiàn), 還真是"有點(diǎn)意思"啊
錯(cuò)誤之處, 還望指出