你的位置:首页 > Java教程

[Java教程]ThreadLocal 源码剖析


ThreadLocal是Java语言提供的用于支持线程局部变量的类。所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量(每个线程一个拷贝)。在各个Java web的各种框架中ThreadLocal几乎已经被用烂了,spring中有使用,mybatis中也有使用,hibernate中也有使用,甚至我们写个分页也用ThreadLocal来传递参数......这也从侧面说明了ThreadLocal十分的给力。

从使用者的角度而言,一般我们可以将ThreadLocal看做是一个:ConcurrentHashMap<Thread, Object>,以Thread引用为key, 来保存本线程的局部变量。但是从实现的角度而言,ThreadLocal的实现根本就不是这样的。下面从源码分析ThreadLocal的实现。

1. 既然是线程局部变量,那么理所当然就应该存储在自己的线程对象中,我们可以从 Thread 的源码中找到线程局部变量存储的地方:

public class Thread implements Runnable {  /* Make sure registerNatives is the first thing <clinit> does. */  private static native void registerNatives();  static {    registerNatives();  }  // ... ...  /* ThreadLocal values pertaining to this thread. This map is maintained   * by the ThreadLocal class. */  ThreadLocal.ThreadLocalMap threadLocals = null;  /*   * InheritableThreadLocal values pertaining to this thread. This map is   * maintained by the InheritableThreadLocal class.   */  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

 我们可以看到线程局部变量是存储在Thread对象的 threadLocals 属性中,而 threadLocals 属性是一个 ThreadLocal.ThreadLocalMap 对象。

2. 我们接着看 ThreadLocal.ThreadLocalMap 是何方神圣

  /**   * ThreadLocalMap is a customized hash map suitable only for   * maintaining thread local values. No operations are exported   * outside of the ThreadLocal class. The class is package private to   * allow declaration of fields in class Thread. To help deal with   * very large and long-lived usages, the hash table entries use   * WeakReferences for keys. However, since reference queues are not   * used, stale entries are guaranteed to be removed only when   * the table starts running out of space.   */  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;    // ... ...    /**     * Construct a new map initially containing (firstKey, firstValue).     * ThreadLocalMaps are constructed lazily, so we only create     * one when we have at least one entry to put in it.     */    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {      table = new Entry[INITIAL_CAPACITY];      int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);      table[i] = new Entry(firstKey, firstValue);      size = 1;      setThreshold(INITIAL_CAPACITY);    }

可以看到ThreadLocal.ThreadLocalMap 是 ThreadLocal 的一个静态内部类。ThreadLocalMap从字面上可以就可以看出这是一个保存ThreadLocal对象的map(其实是以它为Key),没错,不过是经过了两层包装的ThreadLocal对象。第一层包装是使用 WeakReference<ThreadLocal<?>> 将ThreadLocal对象变成一个弱引用的对象;第二层包装是 定义了一个专门的类 Entry 来扩展 WeakReference<ThreadLocal<?>>:

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

类 Entry 很显然是一个保存map键值对的实体,ThreadLocal<?>为key, 要保存的线程局部变量的值为value。super(k)调用的WeakReference的构造函数,表示将ThreadLocal<?>对象转换成弱引用对象,用做key。

从 ThreadLocalMap 的构造函数:

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {      table = new Entry[INITIAL_CAPACITY];      int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);      table[i] = new Entry(firstKey, firstValue);      size = 1;      setThreshold(INITIAL_CAPACITY);    }

可以看出,ThreadLocalMap这个map的实现是使用一个数组 private Entry[] table 来保存键值对的实体,初始大小为16,ThreadLocalMap自己实现了如何从 key  到 value 的映射:

firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)

 

  /**   * ThreadLocals rely on per-thread linear-probe hash maps attached   * to each thread (Thread.threadLocals and   * inheritableThreadLocals). The ThreadLocal objects act as keys,   * searched via threadLocalHashCode. This is a custom hash code   * (useful only within ThreadLocalMaps) that eliminates collisions   * in the common case where consecutively constructed ThreadLocals   * are used by the same threads, while remaining well-behaved in   * less common cases.   */  private final int threadLocalHashCode = nextHashCode();  /**   * The next hash code to be given out. Updated atomically. Starts at   * zero.   */  private static AtomicInteger nextHashCode = new AtomicInteger();  /**   * The difference between successively generated hash codes - turns   * implicit sequential thread-local IDs into near-optimally spread   * multiplicative hash values for power-of-two-sized tables.   */  private static final int HASH_INCREMENT = 0x61c88647;  /**   * Returns the next hash code.   */  private static int nextHashCode() {    return nextHashCode.getAndAdd(HASH_INCREMENT);  }

 

使用一个 static 的原子属性 AtomicInteger nextHashCode,通过每次增加 HASH_INCREMENT = 0x61c88647 ,然后 & (INITIAL_CAPACITY - 1) 取得在数组  private Entry[] table 中的索引。

3. 我们先看一下 Thread 对象中的 ThreadLocal.ThreadLocalMap threadLocals = null; 如何初始化:

 

 

  /**   * 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);  }  /**   * 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;  }  /**   * Create the map associated with a ThreadLocal. Overridden in   * InheritableThreadLocal.   *   * @param t the current thread   * @param firstValue value for the initial entry of the map   */  void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocalMap(this, firstValue);  }

 

ThreadLocal在调用set方法时,如果 getMap(注意是以Thread引用为key) 返回的 t.threadLocals 为null,那么表示该线程的 ThreadLocalMap 还没有初始化,所以调用createMap进行初始化:t.threadLocals = new ThreadLocalMap(this, firstValue);

注意这里使用到了延迟初始化的技术:

    /**     * Construct a new map initially containing (firstKey, firstValue).     * ThreadLocalMaps are constructed lazily, so we only create     * one when we have at least one entry to put in it.     */    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {      table = new Entry[INITIAL_CAPACITY];      int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);      table[i] = new Entry(firstKey, firstValue);      size = 1;      setThreshold(INITIAL_CAPACITY);    }

这里仅仅是初始化了16个元素的引用数组,并没有初始化16个 Entry 对象。而是一个线程中有多少个 ThreadLocal 对象,那么就初始化多少个 Entry 对象来保存它们。

 

到了这里,我们可以思考一下,为什么要这样实现了。为什么要用 ThreadLocalMap 来保存线程局部对象呢?原因是 一个线程拥有的的局部对象可能有很多,这样实现的话,那么不管你一个线程拥有多少个局部变量,都是使用同一个 ThreadLocalMap 来保存的,ThreadLocalMap 中 private Entry[] table 的初始大小是16。超过16时,会扩容。

4. 我们在看一下 ThreadLocal.set 方法:

 

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

 

我们看到是以当前 thread 的引用为 key, 获得 ThreadLocalMap ,然后调用 map.set(this, value); 保存进 private Entry[] table :

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

5. ThreadLocal 涉及到的两个层面的内存自动回收

1)在 ThreadLocal 层面的内存回收:

/* * Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist).

当线程死亡时,那么所有的保存在的线程局部变量就会被回收,其实这里是指线程Thread对象中的 ThreadLocal.ThreadLocalMap threadLocals 会被回收,这是显然的。

 

2)ThreadLocalMap 层面的内存回收:

 

  /**   * ThreadLocalMap is a customized hash map suitable only for   * maintaining thread local values. No operations are exported   * outside of the ThreadLocal class. The class is package private to   * allow declaration of fields in class Thread. To help deal with   * very large and long-lived usages, the hash table entries use   * WeakReferences for keys. However, since reference queues are not   * used, stale entries are guaranteed to be removed only when   * the table starts running out of space.   */

 

如果线程可以活很长的时间,并且该线程保存的线程局部变量有很多(也就是 Entry 对象很多),那么就涉及到在线程的生命期内如何回收 ThreadLocalMap 的内存了,不然的话,Entry对象越多,那么ThreadLocalMap 就会越来越大,占用的内存就会越来越多,所以对于已经不需要了的线程局部变量,就应该清理掉其对应的Entry对象。使用的方式是,Entry对象的key是WeakReference 的包装,当ThreadLocalMap 的 private Entry[] table,已经被占用达到了三分之二时 threshold = 2/3 ,就会尝试回收 Entry 对象,我们可以看到 ThreadLocalMap.set方法中有下面的代码:

 

      if (!cleanSomeSlots(i, sz) && sz >= threshold)        rehash();

 

 cleanSomeSlots 就是进行回收内存:

    /**     * Heuristically scan some cells looking for stale entries.     * This is invoked when either a new element is added, or     * another stale one has been expunged. It performs a     * logarithmic number of scans, as a balance between no     * scanning (fast but retains garbage) and a number of scans     * proportional to number of elements, that would find all     * garbage but would cause some insertions to take O(n) time.     *     * @param i a position known NOT to hold a stale entry. The     * scan starts at the element after i.     *     * @param n scan control: {@code log2(n)} cells are scanned,     * unless a stale entry is found, in which case     * {@code log2(table.length)-1} additional cells are scanned.     * When called from insertions, this parameter is the number     * of elements, but when from replaceStaleEntry, it is the     * table length. (Note: all this could be changed to be either     * more or less aggressive by weighting n instead of just     * using straight log n. But this version is simple, fast, and     * seems to work well.)     *     * @return true if any stale entries have been removed.     */    private boolean cleanSomeSlots(int i, int n) {      boolean removed = false;      Entry[] tab = table;      int len = tab.length;      do {        i = nextIndex(i, len);        Entry e = tab[i];        if (e != null && e.get() == null) {          n = len;          removed = true;          i = expungeStaleEntry(i);        }      } while ( (n >>>= 1) != 0);      return removed;    }

e.get() == null 调用的是 Entry 的父类 WeakReference<ThreadLocal<?>> 的方法:
  /**   * Returns this reference object's referent. If this reference object has   * been cleared, either by the program or by the garbage collector, then   * this method returns <code>null</code>.   *   * @return  The object to which this reference refers, or   *      <code>null</code> if this reference object has been cleared   */  public T get() {    return this.referent;  }

返回 null ,表示 Entry 的 key 已经被回收了,所以可以回收该 Entry 对象了:expungeStaleEntry(i)

 

    /**     * Expunge a stale entry by rehashing any possibly colliding entries     * lying between staleSlot and the next null slot. This also expunges     * any other stale entries encountered before the trailing null. See     * Knuth, Section 6.4     *     * @param staleSlot index of slot known to have null key     * @return the index of the next null slot after staleSlot     * (all between staleSlot and this slot will have been checked     * for expunging).     */    private int expungeStaleEntry(int staleSlot) {      Entry[] tab = table;      int len = tab.length;      // expunge entry at staleSlot      tab[staleSlot].value = null;      tab[staleSlot] = null;      size--;

 

6. ThreadLocal常用的接口:

1)需要制定初始值时,可以覆盖protected T initialValue()方法;

2)public T get();

3)public void set(T value);

4)public void remove();

7. 总结

1)线程中的所有的局部变量其实存储在线程对象自己的一个map中;

2)线程死亡时,线程局部变量会自动回收内存;

3)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量;