ThreadLocal詳解

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 實現原理

 

ThreadLocal的實現是這樣的:每一個Thread 維護一個 ThreadLocalMap 映射表,這個映射表的 key 是 ThreadLocal實例自己,value 是真正須要存儲的 Object

也就是說 ThreadLocal 自己並不存儲值,它只是做爲一個 key 來讓線程從 ThreadLocalMap 獲取 value。值得注意的是圖中的虛線,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用做爲 Key 的,弱引用的對象在 GC 時會被回收。

ThreadLocal爲何會內存泄漏

ThreadLocalMap使用ThreadLocal的弱引用做爲key,若是一個ThreadLocal沒有外部強引用來引用它,那麼系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現keynullEntry,就沒有辦法訪問這些keynullEntryvalue,若是當前線程再遲遲不結束的話,這些keynullEntryvalue就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠沒法回收,形成內存泄漏。

其實,ThreadLocalMap的設計中已經考慮到這種狀況,也加上了一些防禦措施:在ThreadLocalget(),set(),remove()的時候都會清除線程ThreadLocalMap裏全部keynullvalue

可是這些被動的預防措施並不能保證不會內存泄漏:

  • 使用staticThreadLocal,延長了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,getremove的時候會被清除。

比較兩種狀況,咱們能夠發現:因爲ThreadLocalMap的生命週期跟Thread同樣長,若是都沒有手動刪除對應key,都會致使內存泄漏,可是使用弱引用能夠多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除

所以,ThreadLocal內存泄漏的根源是:因爲ThreadLocalMap的生命週期跟Thread同樣長,若是沒有手動刪除對應key就會致使內存泄漏,而不是由於弱引用。

ThreadLocal 最佳實踐

綜合上面的分析,咱們能夠理解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中的應用

http://www.javashuo.com/article/p-usebytan-kq.html

http://www.javashuo.com/article/p-ocxmgqqa-ho.html

相關文章
相關標籤/搜索