1.做用html
Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。java
ThreadLocal最適合按線程多實例(每一個線程對應一個實例)的對象的訪問,而且這個對象不少地方都要用到(線程內傳遞數據 而不用利用方法參數顯式傳遞)數據庫
ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。安全
2.要注意的地方多線程
ThreadLocal並不能解決併發問題。併發
ThreadLocal就像一個管理類或代理類,間接去操做真正的數據。less
真實的數據是存儲在ThreadLocalMap中的,而每一個線程都有一個屬性threadLocals,也就是線程擁有的ThreadLocalMap。ide
ThreadLocalMap內是用Entry來存儲數據的,key是ThreadLocalMap實例,value就是真實的數據。性能
每一個線程只有一個ThreadLocalMap, 能夠存多個ThreadLocal。this
線程內從ThreadLocal內獲取數據時(get), 要先set,不然獲取到的是null,後續天然會報NPE。固然源碼也提供了initialValue方法,咱們只要重寫一下,天然就能夠避免這個NPE了
3.原理和源碼
ThreadLocal類核心方法set、get、initialValue、withInitial、setInitialValue、remove:
1 /** 2 * Returns the current thread's "initial value" for this 3 * thread-local variable. This method will be invoked the first 4 * time a thread accesses the variable with the {@link #get} 5 * method, unless the thread previously invoked the {@link #set} 6 * method, in which case the {@code initialValue} method will not 7 * be invoked for the thread. Normally, this method is invoked at 8 * most once per thread, but it may be invoked again in case of 9 * subsequent invocations of {@link #remove} followed by {@link #get}. 10 * 11 * <p>This implementation simply returns {@code null}; if the 12 * programmer desires thread-local variables to have an initial 13 * value other than {@code null}, {@code ThreadLocal} must be 14 * subclassed, and this method overridden. Typically, an 15 * anonymous inner class will be used. 16 * 17 * @return the initial value for this thread-local 18 */ 19 protected T initialValue() { 20 return null; 21 } 22 23 /** 24 * Creates a thread local variable. The initial value of the variable is 25 * determined by invoking the {@code get} method on the {@code Supplier}. 26 * 27 * @param <S> the type of the thread local's value 28 * @param supplier the supplier to be used to determine the initial value 29 * @return a new thread local variable 30 * @throws NullPointerException if the specified supplier is null 31 * @since 1.8 32 */ 33 public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { 34 return new SuppliedThreadLocal<>(supplier); 35 } 36 37 /** 38 * Creates a thread local variable. 39 * @see #withInitial(java.util.function.Supplier) 40 */ 41 public ThreadLocal() { 42 } 43 44 /** 45 * Returns the value in the current thread's copy of this 46 * thread-local variable. If the variable has no value for the 47 * current thread, it is first initialized to the value returned 48 * by an invocation of the {@link #initialValue} method. 49 * 50 * @return the current thread's value of this thread-local 51 */ 52 public T get() { 53 Thread t = Thread.currentThread(); 54 ThreadLocalMap map = getMap(t); 55 if (map != null) { 56 ThreadLocalMap.Entry e = map.getEntry(this); 57 if (e != null) { 58 @SuppressWarnings("unchecked") 59 T result = (T)e.value; 60 return result; 61 } 62 } 63 return setInitialValue(); 64 } 65 66 /** 67 * Variant of set() to establish initialValue. Used instead 68 * of set() in case user has overridden the set() method. 69 * 70 * @return the initial value 71 */ 72 private T setInitialValue() { 73 T value = initialValue(); 74 Thread t = Thread.currentThread(); 75 ThreadLocalMap map = getMap(t); 76 if (map != null) 77 map.set(this, value); 78 else 79 createMap(t, value); 80 return value; 81 } 82 83 /** 84 * Sets the current thread's copy of this thread-local variable 85 * to the specified value. Most subclasses will have no need to 86 * override this method, relying solely on the {@link #initialValue} 87 * method to set the values of thread-locals. 88 * 89 * @param value the value to be stored in the current thread's copy of 90 * this thread-local. 91 */ 92 public void set(T value) { 93 Thread t = Thread.currentThread(); 94 ThreadLocalMap map = getMap(t); 95 if (map != null) 96 map.set(this, value); 97 else 98 createMap(t, value); 99 } 100 101 /** 102 * Removes the current thread's value for this thread-local 103 * variable. If this thread-local variable is subsequently 104 * {@linkplain #get read} by the current thread, its value will be 105 * reinitialized by invoking its {@link #initialValue} method, 106 * unless its value is {@linkplain #set set} by the current thread 107 * in the interim. This may result in multiple invocations of the 108 * {@code initialValue} method in the current thread. 109 * 110 * @since 1.5 111 */ 112 public void remove() { 113 ThreadLocalMap m = getMap(Thread.currentThread()); 114 if (m != null) 115 m.remove(this); 116 }
createMap和getMap
1 /** 2 * Create the map associated with a ThreadLocal. Overridden in 3 * InheritableThreadLocal. 4 * 5 * @param t the current thread 6 * @param firstValue value for the initial entry of the map 7 */ 8 void createMap(Thread t, T firstValue) { 9 t.threadLocals = new ThreadLocalMap(this, firstValue); 10 } 11 12 13 ThreadLocalMap getMap(Thread t) { 14 return t.threadLocals; 15 } 16
Thread.threadLocals和Thread.inheritableThreadLocals
1 public 2 class Thread implements Runnable { 3 /*...其餘屬性...*/ 4 5 /* ThreadLocal values pertaining to this thread. This map is maintained 6 * by the ThreadLocal class. */ 7 ThreadLocal.ThreadLocalMap threadLocals = null; 8 9 /* 10 * InheritableThreadLocal values pertaining to this thread. This map is 11 * maintained by the InheritableThreadLocal class. 12 */ 13 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
4.Thread同步機制的比較
ThreadLocal和線程同步機制相比有什麼優點呢?
Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。
在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
而ThreadLocal則從另外一個角度來解決多線程的併發訪問。ThreadLocal會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,能夠把不安全的變量封裝進ThreadLocal。
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。
Spring使用ThreadLocal解決線程安全問題咱們知道在通常狀況下,只有無狀態的Bean才能夠在多線程環境下共享,在Spring中,絕大部分Bean均可以聲明爲singleton做用域。就是由於Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態採用ThreadLocal進行處理,讓它們也成爲線程安全的狀態,由於有狀態的Bean就能夠在多線程中共享了。
通常的Web應用劃分爲展示層、服務層和持久層三個層次,在不一樣的層中編寫對應的邏輯,下層經過接口向上層開放功能調用。在通常狀況下,從接收請求到返回響應所通過的全部程序調用都同屬於一個線程。
同一線程貫通三層這樣你就能夠根據須要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,全部關聯的對象引用到的都是同一個變量。
上述參考:楓之逆 原文:https://blog.csdn.net/lufeng20/article/details/24314381
5.內存泄漏問題
ThreadLocal
的實現是這樣的:每一個Thread
維護一個 ThreadLocalMap
映射表,這個映射表的 key
是 ThreadLocal
實例自己,value
是真正須要存儲的 Object
。
也就是說 ThreadLocal
自己並不存儲值,它只是做爲一個 key
來讓線程從 ThreadLocalMap
獲取 value
。值得注意的是圖中的虛線,表示 ThreadLocalMap
是使用 ThreadLocal
的弱引用做爲 Key
的,弱引用的對象在 GC 時會被回收。
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。
下面咱們分兩種狀況討論:
ThreadLocal
的對象被回收了,可是ThreadLocalMap
還持有ThreadLocal
的強引用,若是沒有手動刪除,ThreadLocal
不會被回收,致使Entry
內存泄漏。ThreadLocal
的對象被回收了,因爲ThreadLocalMap
持有ThreadLocal
的弱引用,即便沒有手動刪除,ThreadLocal
也會被回收。value
在下一次ThreadLocalMap
調用set
,get
,remove
的時候會被清除。比較兩種狀況,咱們能夠發現:因爲ThreadLocalMap
的生命週期跟Thread
同樣長,若是都沒有手動刪除對應key
,都會致使內存泄漏,可是使用弱引用能夠多一層保障:弱引用ThreadLocal
不會內存泄漏,對應的value
在下一次ThreadLocalMap
調用set
,get
,remove
的時候會被清除。
所以,ThreadLocal
內存泄漏的根源是:因爲ThreadLocalMap
的生命週期跟Thread
同樣長,若是沒有手動刪除對應key
就會致使內存泄漏,而不是由於弱引用。
綜合上面的分析,咱們能夠理解ThreadLocal
內存泄漏的來龍去脈,那麼怎麼避免內存泄漏呢?
ThreadLocal
,都調用它的remove()
方法,清除數據。在使用線程池的狀況下,沒有及時清理ThreadLocal
,不只是內存泄漏的問題,更嚴重的是可能致使業務邏輯出現問題。因此,使用ThreadLocal
就跟加鎖完要解鎖同樣,用完就清理。
上面關於ThreadLocal內在泄漏的分析 摘自:http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/
關於內存泄漏的問題他還有一篇實例分析 => ThreadLocal 內存泄露的實例分析
6. ThreadLocal應用場景
一、數據庫鏈接池實現
二、有時候ThreadLocal也能夠用來避免一些參數傳遞,經過ThreadLocal來訪問對象
三、在某些狀況下提高性能和安全,如:SimpleDateFormat
參考:https://blog.csdn.net/u012834750/article/details/71646700
7.關於ThreadLocal在Spring中的應用