ThreadLocal的使用和坑點

概念:

ThreadLocal的概念:摘自ThreaLocal的註釋java

This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).

這段話的大概意思是ThreadLocal是保存的線程的本地變量,訪問get/set方法都是對線程獨立的。
大白話就是ThreadLocal是和線程相關的,在一個線程沒有結束以前,在任意方法中get/set在ThreadLocal中設置的值都是隻和當前線程有關。
所以呢,ThreadLocal的使用場景也能夠推測出來,能夠用來在一個線程中傳遞參數,或者某些狀況(好比session,數據庫操做句柄)只跟線程相關的時候來使用。數據庫

源碼分析:

咱們接下來在看看源代碼中,ThreadLocal是什麼樣的?
簡答的貼了一下類和經常使用的幾個方法的源代碼(此博客是基於JDK1.8)
類定義:session

public class ThreadLocal<T>

從類定義上能夠看出ThreadLocal是支持泛型的ide

ThreadLocalMap:函數

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    // 後面代碼省略
}

這裏首先咱們要看到ThreadLocal中有一個靜態類叫作ThreadLocalMap,它裏面有一個靜態類Entry(能夠類比Map中的entry,保存實際的key和value,),ThreadLocalMap其實就是保存了ThreadLocal調用set方法設置的value,key就是ThreadLocal。
總結一下就是當咱們使用ThreadLocal的set方法時,ThreadLocal爲key,保存的泛型對象爲value,存到了ThreadLocal的內部類ThreadLocalMap中,而後ThreaLocalMap的鍵值對其實是放在靜態類Entry裏面。這裏稍微提一句
Entry是繼承了WeakReference弱引用,key是被弱引用的構造函數給建立的,value是強引用。(java的四種引用能夠參考我以前寫的文章Java四種引用簡介源碼分析

get:post

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方法能夠看出,會先獲取當前線程,而後獲取ThreadLocalMap,而後把值從ThreadLocalMap中取出來,若是沒有ThreadLocalMap就去調用setInitialValue()設置完初始值,並返回。
set:this

public void set(T value) {
      Thread t = Thread.currentThread();
      ThreadLocalMap map = getMap(t);
      if (map != null)
          map.set(this, value);
      else
          createMap(t, value);
  }

set方法根據當前的線程從ThreadLocalMap中取得,沒有map就建立一個。thread中的 ThreadLocal.ThreadLocalMap threadLocals變量會指向剛纔建立的ThreadLocalMap。線程

其餘用法

1.初始化值
這樣看下來,應該對ThreadLocal的實現和原理有了一個大概的認識。這裏提一句,ThreadLocal直接new出來,而後去get的值是null。某些狀況下全部的線程都須要有一個初始化的值,這時候能夠重寫 protected T initialValue()方法,以下:code

private static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>() {
       @Override
       protected String initialValue() {
           return "override initialValue的初始值";
       }
   };

或者jdk1.8能夠使用ThreadLocal.withInitial初始化

private static ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(() -> "withInitial的初始值");

這樣全部的線程使用ThreadLocal的get都會是相同的一個初始化值。

2.多個線程有能夠相同的值
能夠使用InheritableThreadLocal,父線程中設置的值,子線程中能夠訪問到。這個用法和上面差很少就不舉例子了,有興趣的能夠本身研究下~

坑點:

1.上面看ThreadLocalMap的時候,我們知道key是弱引用,gc的時候key會被回收,可是value和ThreadLocalMap的引用不會被回收。若是這種狀況的Thread不少,並且一直沒有執行完,就可能會出現內存泄漏。
2.在使用線程池的時候,當使用ThreadLocal調用set方法後,而後沒有調用remove的話,由於線程池的線程是複用的,若是同一個線程再去調用get方法可能拿到的值並非當時set進去的,致使程序數據異常之類的。儘可能用完值後就remove掉。

總結:

1.ThreadLocal在同一個線程傳值,或者只跟線程相關的場景使用
2.初始化ThreadLocal的值能夠用ThreadLocal.withInitial,或重寫initialValue()
3.父子線程共享相同的值使用InheritableThreadLocal
4.無論是正常使用仍是線程池使用ThreadLocal都必定要使用完就remove,不然會內存泄漏或者數據出錯

相關文章
相關標籤/搜索