博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ThreadLocal 源码剖析
阅读量:4656 次
发布时间:2019-06-09

本文共 14880 字,大约阅读时间需要 49 分钟。

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 
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
> { /** 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
> { /** 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 对象。而是一个线程中有多少个线程局部对象要保存,那么就初始化多少个 Entry 对象来保存它们

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

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(也就是线程拥有的局部变量超过了10个) ,就会尝试回收 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
> 的方法:
/**     * 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 null.     *     * @return   The object to which this reference refers, or     *           null 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为线程局部变量; 

     key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;

4)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收;

 

转载于:https://www.cnblogs.com/digdeep/p/4510875.html

你可能感兴趣的文章