注:在《透徹理解Spring事務設計思想之手寫實現》中,已經向你們揭示了Spring就是利用ThreadLocal來實現一個線程中的Connection是同一個,從而保證了事務。本篇博客將帶你們來深刻分析ThreadLocal的實現原理。java
ThreadLocal,顧名思義,它不是一個線程,而是線程的一個本地化對象。當工做於多線程中的對象使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程分配一個獨立的變量副本。因此每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中「Local」所要表達的意思。
線程局部變量並非Java的新發明,不少語言(如IBM XL、FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供語言級支持,而以一種變通的方法,經過ThreadLocal的類提供支持。因此,在Java中編寫線程局部變量的代碼相對來講要笨拙一些,這也是爲何線程局部變量沒有在Java開發者中獲得很好普及的緣由。 數組
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式:訪問串行化,對象共享化。而ThreadLocal採用了「以空間換時間」的方式:訪問並行化,對象獨享化。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。 安全
原理圖:數據結構
ThreadLocal的實現是這樣的:每一個Thread 維護一個 ThreadLocalMap 映射表,這個映射表的 key 是 ThreadLocal實例自己,value 是真正須要存儲的 Object。
也就是說 ThreadLocal 自己並不存儲值,它只是做爲一個 key 來讓線程從 ThreadLocalMap 獲取 value。值得注意的是圖中的虛線,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用做爲 Key 的,弱引用的對象在 GC 時會被回收。多線程
/* * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); } /* * 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; } /* * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }
/* * 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); } }
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
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; ... }
從名字上看,能夠猜到它也是一個相似HashMap的數據結構,可是在ThreadLocal中,並沒實現Map接口。在ThreadLoalMap中,也是初始化一個大小16的Entry數組,Entry對象用來保存每個key-value鍵值對,只不過這裏的key永遠都是ThreadLocal對象,是否是很神奇,經過ThreadLocal對象的set方法,結果把ThreadLocal對象本身當作key,放進了ThreadLoalMap中。ide
沒有鏈表結構,那發生hash衝突了怎麼辦?函數
先看看ThreadLoalMap中插入一個key-value的實現this
/* * 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(); } }
每一個ThreadLocal對象都有一個hash值 threadLocalHashCode
,每初始化一個ThreadLocal對象,hash值就增長一個固定的大小 0x61c88647
。spa
在插入過程當中,根據ThreadLocal對象的hash值,定位到table中的位置i,過程以下: 一、若是當前位置是空的,那麼正好,就初始化一個Entry對象放在位置i上; 二、不巧,位置i已經有Entry對象了,若是這個Entry對象的key正好是即將設置的key,那麼從新設置Entry中的value; 三、很不巧,位置i的Entry對象,和即將設置的key不要緊,那麼只能找下一個空位置;線程
這樣的話,在get的時候,也會根據ThreadLocal對象的hash值,定位到table中的位置,而後判斷該位置Entry對象中的key是否和get的key一致,若是不一致,就判斷下一個位置
能夠發現,set和get若是衝突嚴重的話,效率很低,由於ThreadLoalMap是Thread的一個屬性,因此即便在本身的代碼中控制了設置的元素個數,但仍是不能控制其它代碼的行爲。
能夠從ThreadLocal的get函數中看出來,其中getmap函數是用t做爲參數,這裏t就是當前執行的線程。
從而得知,get函數就是從當前線程的threadlocalmap中取出當前線程對應的變量的副本【注意,變量是保存在線程中的,而不是保存在ThreadLocal變量中】。當前線程中,有一個變量引用名字是threadLocals,這個引用是在ThreadLocal類中createmap函數內初始化的。每一個線程都有一個這樣的threadLocals引用的ThreadLocalMap,以ThreadLocal和ThreadLocal對象聲明的變量類型做爲參數。這樣,咱們所使用的ThreadLocal變量的實際數據,經過get函數取值的時候,就是經過取出Thread中threadLocals引用的map,而後從這個map中根據當前threadLocal做爲參數,取出數據。
ThreadLocalMap使用ThreadLocal的弱引用做爲key,若是一個ThreadLocal沒有外部強引用來引用它,那麼系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key爲null的Entry,就沒有辦法訪問這些key爲null的Entry的value,若是當前線程再遲遲不結束的話,這些key爲null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠沒法回收,形成內存泄漏。
其實,ThreadLocalMap的設計中已經考慮到這種狀況,也加上了一些防禦措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap裏全部key爲null的value。
可是這些被動的預防措施並不能保證不會內存泄漏:
使用static的ThreadLocal,延長了ThreadLocal的生命週期,可能致使的內存泄漏(參考ThreadLocal 內存泄露的實例分析)。
分配使用了ThreadLocal又再也不調用get(),set(),remove()方法,那麼就會致使內存泄漏。
從表面上看內存泄漏的根源在於使用了弱引用。網上的文章大多着重分析ThreadLocal使用了弱引用會致使內存泄漏,可是另外一個問題也一樣值得思考:爲何使用弱引用而不是強引用?
咱們先來看看官方文檔的說法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
爲了應對很是大和長時間的用途,哈希表使用弱引用的 key。
下面咱們分兩種狀況討論:
key 使用強引用:引用的ThreadLocal的對象被回收了,可是ThreadLocalMap還持有ThreadLocal的強引用,若是沒有手動刪除,ThreadLocal不會被回收,致使Entry內存泄漏。
key 使用弱引用:引用的ThreadLocal的對象被回收了,因爲ThreadLocalMap持有ThreadLocal的弱引用,即便沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
比較兩種狀況,咱們能夠發現:因爲ThreadLocalMap的生命週期跟Thread同樣長,若是都沒有手動刪除對應key,都會致使內存泄漏,可是使用弱引用能夠多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
所以,ThreadLocal內存泄漏的根源是:因爲ThreadLocalMap的生命週期跟Thread同樣長,若是沒有手動刪除對應key就會致使內存泄漏,而不是由於弱引用。
每次使用完ThreadLocal,都調用它的remove()方法,清除數據。
在使用線程池的狀況下,沒有及時清理ThreadLocal,不只是內存泄漏的問題,更嚴重的是可能致使業務邏輯出現問題。因此,使用ThreadLocal就跟加鎖完要解鎖同樣,用完就清理。
ThreadLocal在Spring中發揮着重要的做用,Spring使用ThreadLocal解決線程安全問題,在管理request做用域的Bean、事務管理、任務調度、AOP等模塊都出現了它們的身影,起着舉足輕重的做用。要想了解Spring事務管理的底層技術,ThreadLocal是必須攻克的山頭堡。