ThreadLocal是java語(yǔ)言提供的用于支持線程局部變量的類(lèi)。所謂的線程局部變量,就是僅僅只能被本線程訪問(wèn),不能在線程之間進(jìn)行共享訪問(wèn)的變量(每個(gè)線程一個(gè)拷貝)。在各個(gè)Java web的各種框架中ThreadLocal幾乎已經(jīng)被用爛了,sPRing中有使用,mybatis中也有使用,hibernate中也有使用,甚至我們寫(xiě)個(gè)分頁(yè)也用ThreadLocal來(lái)傳遞參數(shù)......這也從側(cè)面說(shuō)明了ThreadLocal十分的給力。
從使用者的角度而言,一般我們可以將ThreadLocal看做是一個(gè):ConcurrentHashMap<Thread, Object>,以Thread引用為key, 來(lái)保存本線程的局部變量。但是從實(shí)現(xiàn)的角度而言,ThreadLocal的實(shí)現(xiàn)根本就不是這樣的。下面從源碼分析ThreadLocal的實(shí)現(xiàn)。
1. 既然是線程局部變量,那么理所當(dāng)然就應(yīng)該存儲(chǔ)在自己的線程對(duì)象中,我們可以從 Thread 的源碼中找到線程局部變量存儲(chǔ)的地方:
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;我們可以看到線程局部變量是存儲(chǔ)在Thread對(duì)象的 threadLocals 屬性中,而 threadLocals 屬性是一個(gè) ThreadLocal.ThreadLocalMap 對(duì)象。
2. 我們接著看 ThreadLocal.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; } } /** * 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 的一個(gè)靜態(tài)內(nèi)部類(lèi)。ThreadLocalMap從字面上就可以看出這是一個(gè)保存ThreadLocal對(duì)象的map(其實(shí)是以它為Key),沒(méi)錯(cuò),不過(guò)是經(jīng)過(guò)了兩層包裝的ThreadLocal對(duì)象。第一層包裝是使用 WeakReference<ThreadLocal<?>> 將ThreadLocal對(duì)象變成一個(gè)弱引用的對(duì)象;第二層包裝是 定義了一個(gè)專(zhuān)門(mén)的類(lèi) Entry 來(lái)擴(kuò)展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; } }類(lèi) Entry 很顯然是一個(gè)保存map鍵值對(duì)的實(shí)體,ThreadLocal<?>為key, 要保存的線程局部變量的值為value。super(k)調(diào)用的WeakReference的構(gòu)造函數(shù),表示將ThreadLocal<?>對(duì)象轉(zhuǎn)換成弱引用對(duì)象,用做key。
從 ThreadLocalMap 的構(gòu)造函數(shù):
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這個(gè)map的實(shí)現(xiàn)是使用一個(gè)數(shù)組 private Entry[] table 來(lái)保存鍵值對(duì)的實(shí)體,初始大小為16,ThreadLocalMap自己實(shí)現(xiàn)了如何從 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); }
使用一個(gè) static 的原子屬性 AtomicInteger nextHashCode,通過(guò)每次增加 HASH_INCREMENT = 0x61c88647 ,然后 & (INITIAL_CAPACITY - 1) 取得在數(shù)組 private Entry[] table 中的索引。
3. 我們先看一下 Thread 對(duì)象中的 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在調(diào)用set方法時(shí),如果 getMap(注意是以Thread引用為key) 返回的 t.threadLocals 為null,那么表示該線程的 ThreadLocalMap 還沒(méi)有初始化,所以調(diào)用createMap進(jìn)行初始化:t.threadLocals = new ThreadLocalMap(this, firstValue);
注意這里使用到了延遲初始化的技術(shù):
/** * 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個(gè)元素的引用數(shù)組,并沒(méi)有初始化16個(gè) Entry 對(duì)象。而是一個(gè)線程中有多少個(gè)線程局部對(duì)象要保存,那么就初始化多少個(gè) Entry 對(duì)象來(lái)保存它們。
到了這里,我們可以思考一下,為什么要這樣實(shí)現(xiàn)了。為什么要用 ThreadLocalMap 來(lái)保存線程局部對(duì)象呢?原因是一個(gè)線程擁有的的局部對(duì)象可能有很多,這樣實(shí)現(xiàn)的話(huà),那么不管你一個(gè)線程擁有多少個(gè)局部變量,都是使用同一個(gè) ThreadLocalMap 來(lái)保存的,ThreadLocalMap 中 private Entry[] table 的初始大小是16。超過(guò)容量的2/3時(shí),會(huì)擴(kuò)容。
4. 我們?cè)诳匆幌?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); }我們看到是以當(dāng)前 thread 的引用為 key, 獲得 ThreadLocalMap ,然后調(diào)用 map.set(this, value); 保存進(jìn) 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 涉及到的兩個(gè)層面的內(nèi)存自動(dòng)回收
1)在 ThreadLocal 層面的內(nèi)存回收:
/* * 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).當(dāng)線程死亡時(shí),那么所有的保存在的線程局部變量就會(huì)被回收,其實(shí)這里是指線程Thread對(duì)象中的 ThreadLocal.ThreadLocalMap threadLocals 會(huì)被回收,這是顯然的。
2)ThreadLocalMap 層面的內(nèi)存回收:
/** * 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. */
如果線程可以活很長(zhǎng)的時(shí)間,并且該線程保存的線程局部變量有很多(也就是 Entry 對(duì)象很多),那么就涉及到在線程的生命期內(nèi)如何回收 ThreadLocalMap 的內(nèi)存了,不然的話(huà),Entry對(duì)象越多,那么ThreadLocalMap 就會(huì)越來(lái)越大,占用的內(nèi)存就會(huì)越來(lái)越多,所以對(duì)于已經(jīng)不需要了的線程局部變量,就應(yīng)該清理掉其對(duì)應(yīng)的Entry對(duì)象。使用的方式是,Entry對(duì)象的key是WeakReference 的包裝,當(dāng)ThreadLocalMap 的 private Entry[] table,已經(jīng)被占用達(dá)到了三分之二時(shí) threshold = 2/3(也就是線程擁有的局部變量超過(guò)了10個(gè)) ,就會(huì)嘗試回收 Entry 對(duì)象,我們可以看到 ThreadLocalMap.set方法中有下面的代碼:
if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();
cleanSomeSlots 就是進(jìn)行回收內(nèi)存:
/** * 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 調(diào)用的是 Entry 的父類(lèi) 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 已經(jīng)被回收了,所以可以回收該 Entry 對(duì)象了: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)需要制定初始值時(shí),可以覆蓋protected T initialValue()方法;
2)public T get();
3)public void set(T value);
4)public void remove();
7. 總結(jié)
1)一個(gè)線程中的所有的局部變量其實(shí)存儲(chǔ)在該線程自己的同一個(gè)map屬性中;
2)線程死亡時(shí),線程局部變量會(huì)自動(dòng)回收內(nèi)存;
3)線程局部變量時(shí)通過(guò)一個(gè) Entry 保存在map中,該Entry 的key是一個(gè) WeakReference包裝的ThreadLocal, value為線程局部變量;
key 到 value 的映射是通過(guò):ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 來(lái)完成的;
4)當(dāng)線程擁有的局部變量超過(guò)了容量的2/3(沒(méi)有擴(kuò)大容量時(shí)是10個(gè)),會(huì)涉及到ThreadLocalMap中Entry的回收;
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注