ThreadLocal使用方法&實現原理

Java中的ThreadLocal類容許咱們建立只能被同一個線程讀寫的變量。所以,若是一段代碼含有一個ThreadLocal變量的引用,即便兩個線程同時執行這段代碼,它們也沒法訪問到對方的ThreadLocal變量。java

 

建立ThreadLocal變量

private ThreadLocal myThreadLocal = new ThreadLocal();

咱們能夠看到,經過這段代碼實例化了一個ThreadLocal對象。咱們只須要實例化對象一次,而且也不須要知道它是被哪一個線程實例化。雖然全部的線程都能訪問到這個ThreadLocal實例,可是每一個線程卻只能訪問到本身經過調用ThreadLocal的set()方法設置的值。即便是兩個不一樣的線程在同一個ThreadLocal對象上設置了不一樣的值,他們仍然沒法訪問到對方的值。併發

 

訪問ThreadLocal變量

一旦建立了一個ThreadLocal變量,你能夠經過以下代碼設置某個須要保存的值:ide

myThreadLocal.set("A thread local value」);

能夠經過下面方法讀取保存在ThreadLocal變量中的值:函數

String threadLocalValue = (String) myThreadLocal.get();

 

爲ThreadLocal指定泛型類型

咱們能夠建立一個指定泛型類型的ThreadLocal對象,這樣咱們就不須要每次對使用get()方法返回的值做強制類型轉換了。下面展現了指定泛型類型的ThreadLocal例子:ui

private ThreadLocal myThreadLocal = new ThreadLocal<String>();

如今咱們只能往ThreadLocal對象中存入String類型的值了。而且咱們從ThreadLocal中獲取值的時候也不須要強制類型轉換了。this

 

初始化ThreadLocal變量的值

因爲在ThreadLocal對象中設置的值只能被設置這個值的線程訪問到,線程沒法在ThreadLocal對象上使用set()方法保存一個初始值,而且這個初始值能被全部線程訪問到。spa

可是咱們能夠經過建立一個ThreadLocal的子類而且重寫initialValue()方法,來爲一個ThreadLocal對象指定一個初始值。就像下面代碼展現的那樣:線程

private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
        return "This is the initial value";
    }
};

 

多個線程局部變量 ThreadLocal 的使用

不一樣的線程局部變量,好比說聲明瞭n個(n>=2)這樣的線程局部變量threadlocal,那麼在Thread中的threadlocals中是怎麼存儲的呢?threadlocalmap中是怎麼操做的?code

在ThreadLocal的set函數中,能夠看到,其中的map.set(this, value);把當前的threadlocal傳入到map中做爲key,也就是說,在不一樣的線程的threadlocals變量中,都會有一個以你所聲明的那個線程局部變量threadlocal做爲鍵的key-value。假設說聲明瞭N個這樣的線程局部變量變量,那麼在線程的ThreadLocalMap中就會有n個分別以你的線程局部變量做爲key的鍵值對。orm

 

ThreadLocal實現原理

public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
}

在set方法內部咱們看到,首先經過getMap(Thread t)方法獲取一個和當前線程相關的ThreadLocalMap,而後將變量的值設置到這個ThreadLocalMap對象中,固然若是獲取到的ThreadLocalMap對象爲空,就經過createMap方法建立。

線程隔離的祕密,就在於ThreadLocalMap這個類。

/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal> {
        /**
         * The value associated with this ThreadLocal.
         */
        Object value;

        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }

    ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
        table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    /**
     * Get the entry associated with key.  This method
     * itself handles only the fast path: a direct hit of existing
     * key. It otherwise relays to getEntryAfterMiss.  This is
     * designed to maximize performance for direct hits, in part
     * by making this method readily inlinable.
     *
     * @param key the thread local object
     * @return the entry associated with key, or null if no such
     */
    private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        ThreadLocal.ThreadLocalMap.Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

}

ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.),每一個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類經過操做每個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不一樣線程中的隔離。由於每一個線程的變量都是本身特有的,徹底不會有併發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。

爲了加深理解,咱們接着看上面代碼中出現的getMap和createMap方法的實現:

ThreadLocalMap getMap(Thread t) {
	return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}

代碼已經說的很是直白,就是獲取和設置Thread內的一個叫threadLocals的變量,而這個變量的類型就是ThreadLocalMap,這樣進一步驗證了上文中的觀點:每一個線程都有本身獨立的ThreadLocalMap對象。打開java.lang.Thread類的源代碼,咱們能獲得更直觀的證實:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

那麼接下來再看一下ThreadLocal類中的get()方法,代碼是這麼說的:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    return setInitialValue();
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

這兩個方法的代碼告訴咱們,在獲取和當前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象爲鍵進行查找的,這固然和前面set()方法的代碼是相呼應的。

設置到這些線程中的隔離變量,會不會致使內存泄漏呢?ThreadLocalMap對象保存在Thread對象中,當某個線程終止後,存儲在其中的線程隔離的變量,也將做爲Thread實例的垃圾被回收掉,因此徹底不用擔憂內存泄漏的問題。

最後再提一句,ThreadLocal變量的這種隔離策略,也不是任何狀況下都能使用的。若是多個線程併發訪問的對象實例只容許也只能建立那麼一個,那就沒有別的辦法了,老老實實的使用同步機制來訪問吧。

==========END==========

相關文章
相關標籤/搜索