Java ThreadLocal 的使用與源碼解析

  • 2019 年 10 月 22 日
  • 筆記

GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用與源碼解析/

ThreadLocal 主要解決的是每個線程綁定自己的值,可以將 ThreadLocal 看成全局存放數據的盒子,盒子中存儲每個線程的私有數據。

驗證線程變量的隔離性

import static java.lang.System.out;    public class Run {        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();        static class Work extends Thread {            @Override          public void run() {              threadLocal.set(0);              for (int i = 1; i <= 5; i++) {                  // 獲取數據                  int sum = threadLocal.get();                  out.printf("%s's sum = %sn", getName(), threadLocal.get());                  sum += i;                  // 寫回數據                  threadLocal.set(sum);              }              out.printf("END %s's sum = %dnn", getName(), threadLocal.get());          }      }        public static void main(String[] args) {          Work work1 = new Work(),                  work2 = new Work();            work1.start();          work2.start();      }  }

運行結果:

Thread-0's sum = null  Thread-1's sum = null  Thread-1's sum = 1  Thread-1's sum = 3  Thread-1's sum = 6  Thread-1's sum = 10  END Thread-1's sum = 15    Thread-0's sum = 1  Thread-0's sum = 3  Thread-0's sum = 6  Thread-0's sum = 10  END Thread-0's sum = 15      Process finished with exit code 0

從結果來看,兩個線程的計算結果一致,ThreadLocal 中隔離了兩個線程的數據。

ThreadLocal 源碼解析

ThreadLocalMap 內部類

ThreadLocal 中有一個 ThreadLocalMap 內部類,所以 ThreadLocal 實際上是使用一個哈希表來存儲每個線程的數據的。

ThreadLocalMapHashMap 不同,其中 Entry 是一個弱引用,這意味着每次垃圾回收運行時都會將儲存的數據回收掉。而且它只使用了數組來存儲鍵值對。

ThreadLocalMap 中的 Entry

static class Entry extends WeakReference<ThreadLocal<?>> {              /** The value associated with this ThreadLocal. */              Object value;                Entry(ThreadLocal<?> k, Object v) {                  super(k);                  value = v;              }          }

get() 方法

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 對象,然後從中取出數據。

這裡的 map.getEntry(this) 看起來很奇怪,在前面有這樣一行代碼:

ThreadLocalMap map = getMap(t);

這個方法獲取當前線程的 ThreadLocalMap 對象,所以,雖然 map.getEntry() 中的 key 總是 ThreadLocal 對象本身,但是每個線程都持有有自己的 ThreadLocalMap 對象。

getMap() 方法

/**   * 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;  }

看到這個方法,get() 方法中 map.getEntry(this) 的迷霧就解開了。這裡可以看到返回的是線程中的 threadLocals 屬性。那麼這裡瞟一眼 Thread 的源碼:

/* ThreadLocal values pertaining to this thread. This map is maintained   * by the ThreadLocal class. */  ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal

其實每次 get() 時都是先獲取了線程自己的 ThreadLocalMap 對象,然後對這個對象進行操作。

set() 方法

public void set(T value) {      Thread t = Thread.currentThread();      ThreadLocalMap map = getMap(t);      if (map != null)          map.set(this, value);      else          // 為當前線程創建一個 ThreadLocalMap 對象          createMap(t, value);  }

set() 方法也是先獲取當前線程自己的 ThreadLocalMap 對象,然後再設置數據。如果獲取的哈希表為 null,則創建一個。

createMap() 方法

void createMap(Thread t, T firstValue) {      t.threadLocals = new ThreadLocalMap(this, firstValue);  }

createMap() 方法創建一個 ThreadLocalMap 對象,該對象由線程持有。

總結

  • ThreadLocal 可以隔離線程的變量,每個線程只能從這個對象中獲取到屬於自己的數據。
  • ThreadLocal 使用哈希表來存儲線程的數據,而且這個哈希表是由線程自己持有的,每次獲取和設值都會先獲取當前線程持有的ThreadLocalMap 對象。
  • ThreadLocalMap 中的 key 總是 ThreadLocal 對象本身。
  • ThreadLocalMap 中的 Entry 是弱引用,每次 GC 運行都會被回收。