Java中的ThreadLocal類容許咱們建立只能被同一個線程讀寫的變量。所以,若是一段代碼含有一個ThreadLocal變量的引用,即便兩個線程同時執行這段代碼,它們也沒法訪問到對方的ThreadLocal變量。java
private ThreadLocal myThreadLocal = new ThreadLocal();
咱們能夠看到,經過這段代碼實例化了一個ThreadLocal對象。咱們只須要實例化對象一次,而且也不須要知道它是被哪一個線程實例化。雖然全部的線程都能訪問到這個ThreadLocal實例,可是每一個線程卻只能訪問到本身經過調用ThreadLocal的set()方法設置的值。即便是兩個不一樣的線程在同一個ThreadLocal對象上設置了不一樣的值,他們仍然沒法訪問到對方的值。併發
一旦建立了一個ThreadLocal變量,你能夠經過以下代碼設置某個須要保存的值:ide
myThreadLocal.set("A thread local value」);
能夠經過下面方法讀取保存在ThreadLocal變量中的值:函數
String threadLocalValue = (String) myThreadLocal.get();
咱們能夠建立一個指定泛型類型的ThreadLocal對象,這樣咱們就不須要每次對使用get()方法返回的值做強制類型轉換了。下面展現了指定泛型類型的ThreadLocal例子:ui
private ThreadLocal myThreadLocal = new ThreadLocal<String>();
如今咱們只能往ThreadLocal對象中存入String類型的值了。而且咱們從ThreadLocal中獲取值的時候也不須要強制類型轉換了。this
因爲在ThreadLocal對象中設置的值只能被設置這個值的線程訪問到,線程沒法在ThreadLocal對象上使用set()方法保存一個初始值,而且這個初始值能被全部線程訪問到。spa
可是咱們能夠經過建立一個ThreadLocal的子類而且重寫initialValue()方法,來爲一個ThreadLocal對象指定一個初始值。就像下面代碼展現的那樣:線程
private ThreadLocal myThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "This is the initial value"; } };
不一樣的線程局部變量,好比說聲明瞭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
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==========