ThreadLocal是Java語言提供的用於支持線程局部變量的類。所謂的線程局部變量,就是僅僅只能被本線程訪問,不能在線程之間進行共享訪問的變量(每一個線程一個拷貝)。在各個Java web的各類框架中ThreadLocal幾乎已經被用爛了,spring中有使用,mybatis中也有使用,hibernate中也有使用,甚至咱們寫個分頁也用ThreadLocal來傳遞參數......這也從側面說明了ThreadLocal十分的給力。java
從使用者的角度而言,通常咱們能夠將ThreadLocal看作是一個:ConcurrentHashMap<Thread, Object>,以Thread引用爲key, 來保存本線程的局部變量。可是從實現的角度而言,ThreadLocal的實現根本就不是這樣的。下面從源碼分析ThreadLocal的實現。web
1. 既然是線程局部變量,那麼理所固然就應該存儲在本身的線程對象中,咱們能夠從 Thread 的源碼中找到線程局部變量存儲的地方:spring
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } // ... ... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
咱們能夠看到線程局部變量是存儲在Thread對象的 threadLocals 屬性中,而 threadLocals 屬性是一個 ThreadLocal.ThreadLocalMap 對象。數組
2. 咱們接着看 ThreadLocal.ThreadLocalMap 是何方神聖mybatis
/** * 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; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; // ... ... /** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
能夠看到ThreadLocal.ThreadLocalMap 是 ThreadLocal 的一個靜態內部類。ThreadLocalMap從字面上就能夠看出這是一個保存ThreadLocal對象的map(實際上是以它爲Key),沒錯,不過是通過了兩層包裝的ThreadLocal對象。第一層包裝是使用 WeakReference<ThreadLocal<?>> 將ThreadLocal對象變成一個弱引用的對象;第二層包裝是 定義了一個專門的類 Entry 來擴展 WeakReference<ThreadLocal<?>>:框架
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
類 Entry 很顯然是一個保存map鍵值對的實體,ThreadLocal<?>爲key, 要保存的線程局部變量的值爲value。super(k)調用的WeakReference的構造函數,表示將ThreadLocal<?>對象轉換成弱引用對象,用作key。less
從 ThreadLocalMap 的構造函數:ide
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
能夠看出,ThreadLocalMap這個map的實現是使用一個數組 private Entry[] table 來保存鍵值對的實體,初始大小爲16,ThreadLocalMap本身實現瞭如何從 key 到 value 的映射: firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)函數
/** * ThreadLocals rely on per-thread linear-probe hash maps attached * to each thread (Thread.threadLocals and * inheritableThreadLocals). The ThreadLocal objects act as keys, * searched via threadLocalHashCode. This is a custom hash code * (useful only within ThreadLocalMaps) that eliminates collisions * in the common case where consecutively constructed ThreadLocals * are used by the same threads, while remaining well-behaved in * less common cases. */ private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
使用一個 static 的原子屬性 AtomicInteger nextHashCode,經過每次增長 HASH_INCREMENT = 0x61c88647 ,而後 & (INITIAL_CAPACITY - 1) 取得在數組 private Entry[] table 中的索引。源碼分析
3. 咱們先看一下 Thread 對象中的 ThreadLocal.ThreadLocalMap threadLocals = null; 如何初始化:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocal在調用set方法時,若是 getMap(注意是以Thread引用爲key) 返回的 t.threadLocals 爲null,那麼表示該線程的 ThreadLocalMap 尚未初始化,因此調用createMap進行初始化:t.threadLocals = new ThreadLocalMap(this, firstValue);
注意這裏使用到了延遲初始化的技術:
/** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }
這裏僅僅是初始化了16個元素的引用數組,並無初始化16個 Entry 對象。而是一個線程中有多少個線程局部對象要保存,那麼就初始化多少個 Entry 對象來保存它們。
到了這裏,咱們能夠思考一下,爲何要這樣實現了。爲何要用 ThreadLocalMap 來保存線程局部對象呢?緣由是一個線程擁有的的局部對象可能有不少,這樣實現的話,那麼無論你一個線程擁有多少個局部變量,都是使用同一個 ThreadLocalMap 來保存的,ThreadLocalMap 中 private Entry[] table 的初始大小是16。超過容量的2/3時,會擴容。
4. 咱們在看一下 ThreadLocal.set 方法:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
咱們看到是以當前 thread 的引用爲 key, 得到 ThreadLocalMap ,而後調用 map.set(this, value); 保存進 private Entry[] table :
/** * Set the value associated with key. * @param key the thread local object * @param value the value to be set */ 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; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
5. ThreadLocal 涉及到的兩個層面的內存自動回收
1)在 ThreadLocal 層面的內存回收:
/* * Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist).
當線程死亡時,那麼全部的保存在的線程局部變量就會被回收,其實這裏是指線程Thread對象中的 ThreadLocal.ThreadLocalMap threadLocals 會被回收,這是顯然的。
2)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. */
若是線程能夠活很長的時間,而且該線程保存的線程局部變量有不少(也就是 Entry 對象不少),那麼就涉及到在線程的生命期內如何回收 ThreadLocalMap 的內存了,否則的話,Entry對象越多,那麼ThreadLocalMap 就會愈來愈大,佔用的內存就會愈來愈多,因此對於已經不須要了的線程局部變量,就應該清理掉其對應的Entry對象。使用的方式是,Entry對象的key是WeakReference 的包裝,當ThreadLocalMap 的 private Entry[] table,已經被佔用達到了三分之二時 threshold = 2/3(也就是線程擁有的局部變量超過了10個) ,就會嘗試回收 Entry 對象,咱們能夠看到 ThreadLocalMap.set方法中有下面的代碼:
if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();
cleanSomeSlots 就是進行回收內存:
/** * Heuristically scan some cells looking for stale entries. * This is invoked when either a new element is added, or * another stale one has been expunged. It performs a * logarithmic number of scans, as a balance between no * scanning (fast but retains garbage) and a number of scans * proportional to number of elements, that would find all * garbage but would cause some insertions to take O(n) time. * * @param i a position known NOT to hold a stale entry. The * scan starts at the element after i. * * @param n scan control: {@code log2(n)} cells are scanned, * unless a stale entry is found, in which case * {@code log2(table.length)-1} additional cells are scanned. * When called from insertions, this parameter is the number * of elements, but when from replaceStaleEntry, it is the * table length. (Note: all this could be changed to be either * more or less aggressive by weighting n instead of just * using straight log n. But this version is simple, fast, and * seems to work well.) * * @return true if any stale entries have been removed. */ private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
e.get() == null 調用的是 Entry 的父類 WeakReference<ThreadLocal<?>> 的方法:
/** * Returns this reference object's referent. If this reference object has * been cleared, either by the program or by the garbage collector, then * this method returns <code>null</code>. * * @return The object to which this reference refers, or * <code>null</code> if this reference object has been cleared */ public T get() { return this.referent; }
返回 null ,表示 Entry 的 key 已經被回收了,因此能夠回收該 Entry 對象了:expungeStaleEntry(i)
/** * Expunge a stale entry by rehashing any possibly colliding entries * lying between staleSlot and the next null slot. This also expunges * any other stale entries encountered before the trailing null. See * Knuth, Section 6.4 * * @param staleSlot index of slot known to have null key * @return the index of the next null slot after staleSlot * (all between staleSlot and this slot will have been checked * for expunging). */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--;
6. ThreadLocal經常使用的接口:
1)須要制定初始值時,能夠覆蓋protected T initialValue()方法;
2)public T get();
3)public void set(T value);
4)public void remove();
7. 總結
1)一個線程中的全部的局部變量其實存儲在該線程本身的同一個map屬性中;
2)線程死亡時,線程局部變量會自動回收內存;
3)線程局部變量時經過一個 Entry 保存在map中,該Entry 的key是一個 WeakReference包裝的ThreadLocal, value爲線程局部變量;
key 到 value 的映射是經過:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 來完成的;
4)當線程擁有的局部變量超過了容量的2/3(沒有擴大容量時是10個),會涉及到ThreadLocalMap中Entry的回收;