天天看點

ThreadLocal從源碼到應用

最早接觸到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裡面的記憶體結構是這樣的:

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為什麼推薦這樣使用:

  1. 推薦用private修飾,是不向外部其他的對象也能引用到,防止幹擾垃圾回收
  2. 推薦使用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

本文版權歸作者和部落格園共有。歡迎轉載,但必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接!