最早接觸到ThreadLocal是在閱讀dianping的Cat-client,當時對它不是很了解,就搜尋了一下,大概了解是一種解決線程安全問題的機制。現在再次閱讀《實戰java高并發程式設計》時,又重新對它有了更深一步的了解。
并發程式很重要的主題就是解決多線程安全的問題,最常見的處理辦法就是引入鎖的機制;但是鎖使得各個線程對臨界區的使用效率變差,于是有了一種新的思路,即每個線程獨立管理某個變量,變量的修改線上程中時獨立的。就好比,以前鎖的機制是100個人簽到,隻有一個簽字薄;而現在ThreadLocal是每個人一張紙。
不過上面的場景,隻是threadLocal的一個應用場景。還有個例子,是在城市裡面倒車。小明去上班要先做公共汽車在做地鐵,如果每次坐車都買票,那麼時間效率很差。于是小明辦理了一張通用的公交卡,公共汽車和地鐵都可以刷。而小藍小紅也有這樣的公交卡,它們的公交卡彼此之間是獨立的。這就是ThreadLocal的作用!
是以說,ThreadLocal并不是解決線程共享問題,而是為了解決單個線程内部變量的獨立性和參數傳遞的問題。
那麼它的原理時什麼樣的呢?
說白了,就是每個線程自己有一個Map,這個Map采用了線性探測法來存儲變量。接下來主要閱讀下代碼吧:
public T get() {
Thread t = Thread.currentThread();
// 擷取目前線程的localmap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 用目前的變量作為key查詢對象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果不存在的話,初始化變量
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
主要時那個getEntry方法:
private Entry getEntry(ThreadLocal<?> key) {
// 通過目前key的hashcode與清單的長度做 &操作,判斷存儲的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 如果不存在的話,進入getEntryAfterMiss方法
// 這種情況,可能是key被回收掉了;也可能是hash沖突了
return getEntryAfterMiss(key, i, e);
}
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;
}
需要注意的是,ThreadLocal裡面的記憶體結構是這樣的:

由于key是弱引用,是以在gc的時候會被回收掉。是以entry中會包含key為null的值,那麼這裡會不會有記憶體洩漏呢?可以看一下expungeStaleEntry方法,在發現有value為null的時候,threadlocal會自動掃描其他的元素,看看有沒有key為null的,如果有的話,一并移除。
如果為null,需要清理對應的value:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
官方推薦使用private static修飾,跟Java大神聊了下,總結一下ThreadLocal為什麼推薦這樣使用:
- 推薦用private修飾,是不向外部其他的對象也能引用到,防止幹擾垃圾回收
- 推薦使用static,我個人的了解是為了把對象存儲到方法去(static修飾的變量會存儲在方法區),這樣雖然内部的Entry是弱引用,但由于變量在方法區,也不會在gc的時候被回收掉。<---個人了解哈,如有不對,還請指正
應用
在cat的代碼中:
public class DefaultMessageManager extends ContainerHolder implements MessageManager, Initializable, LogEnabled {
// we don't use static modifier since MessageManager is configured as singleton
private ThreadLocal<Context> m_context = new ThreadLocal<Context>();
// 每個線程擁有獨立的上下文資訊
private Context getContext() {
if (Cat.isInitialized()) {
Context ctx = m_context.get();
if (ctx != null) {
return ctx;
} else {
if (m_domain != null) {
ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
} else {
ctx = new Context("Unknown", m_hostName, "");
}
m_context.set(ctx);
return ctx;
}
}
return null;
}
@Override
public void end(Transaction transaction) {
Context ctx = getContext();
if (ctx != null && transaction.isStandalone()) {
if (ctx.end(this, transaction)) {
m_context.remove();
}
}
}
作者:xingoo
出處:http://www.cnblogs.com/xing901022
本文版權歸作者和部落格園共有。歡迎轉載,但必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接!