今天在看Spring 3.x企業應用開發實戰,第九章 Spring的事務管理,9.2.2節ThreadLocal的接口方法時,書上有提到Threadlocal的簡單實現,我就去看了下JDK1.8的Threadlocal的源碼。發現實現方式與書中講的並不相同,同時在網上搜索了一下,發現有比較多的人理解錯了。
先看一下容易誤導的解釋:在ThreadLocal類中有一個Map對象,這個Map以每個Thread對象爲鍵,保存了這個線程對應局部變量值,對應的實現方式如下:
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
valueMap.put(Thread.currentThread(), newValue);//①鍵爲線程對象,值爲本線程的變量副本
}
public Object get() {
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);//②返回本線程對應的變量
if (o == null && !valueMap.containsKey(currentThread)) {//③如果在Map中不存在,放到Map 中保存起來。
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove() {
valueMap.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
爲什麼不按照那種有誤的方法實現呢?
看起來似乎是不同線程獲取了各自的值,但是這些值並沒有線程獨立。線程A可以操作線程B對應的值。如果某個線程將保存這些值的Map置爲null了,那麼其他線程也無法訪問了。
實際上是怎樣的呢
我們看ThreadLocal的get()方法源碼。可以看到獲取Threadlocal中的值,是先通過當前線程的線程對象t,獲取t的ThreadlocalMap屬性對象,然後再以Threadlocal對象爲鍵,去獲取ThreadlocalMap中的值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//獲取線程對象的屬性
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//這裏的this是指Threadlocal對象,Threadlocal在類中通常以靜態屬性出現,所以多個線程的Threadlocal指向同一個對象。
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
同時我們查看ThreadLocal源碼中定義的靜態類ThreadLocalMap,其實底層封裝的是一個Entry數組,獲取方式和普通的HashMap不太一樣,如果沒有命中,就直接通過線性搜索,因爲ThreadLocalMap需要保存的Entry並不會太多。
private Entry getEntry(ThreadLocal<?> key) {
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);
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
通過ThreadLocal,每個線程保存自身的數據,不能訪問到其他線程的數據。
ThreadLocalMap的Entry使用ThreadLocal的WeakReference引用作爲Key值,當所有線程運行出ThreadLocal的作用域時,即沒有強引用ThreadLocal時,ThreadLocal就會被回收。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
不是Threadlocal爲每個線程提供了獨立的變量,而是每個線程自己帶了自己獨立的變量。
關於內存泄漏
關於ThreadLocalMap的內存泄漏:如果一個ThreadLocal的生命週期結束,即在ThreadLocal所處的類中沒有了強引用,而Thread沒有結束,在Thread的threadLocals成員變量中,會有一個Entry使用弱引用引用了ThreadLocal作爲key,因爲是弱引用,這個key將被回收。而value是強引用,看起來是會造成泄漏,但是在ThreadLocalMap的set和get方法中,有一些釋放的方法。具體的我也不太懂。
還是老老實實使用ThreadLocal的remove方法比較好。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}