Java ThreadLocal类

2016年08月08日

ThreadLocal

用来支持 *线程本地* 变量, 即每一个线程都拥有它自己的一个变量,线程相互之间不能相互访问.

ThreadLocal对象和Thread线程的关系

每个线程所持有的所有线程本地变量,其实都是放在当前线程中的((ThreadLocal.ThreadLocalMap)Thread.threadLocals).而ThreadLocal对象就像一个包装器, 只是负责更新或删除当前线程中的变量. 从Tread.threadLocals中获取或者删除本地变量时,是直接以当前ThreadLocal对象作为key进行操作的.

在Thread.threadLocals中, ThreadLocal对象的HashCode由上一个ThreadLocal对象的HashCode值加上 0x61c88647 , 据说这样可生成最优散列值…..

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;

本地 变量set过程

    public void set(T value) {
      // 获取当前线程所持有的所有本地变量集合,若集合为空,则新建
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }

ThreadLocalMap中使用 *线性探测法* 解决Hash冲突. 在map.set方法中,若ThreadLocal对象已在hash表中,则覆盖,否则找到第一个为空的槽位,放置对象.

疑问:看循环逻辑中并未体现出对循环次数的限制, 那难道就不会出现hash表已满的情况导致for循环无 法退出么?

答案是,hash表的默认扩容策略为超过hash表容量的2/3,则进行扩容,所以hash表中一定会存在空的槽位,不会出现循环无法退出的情况.

在做扩容前,还会先剔除ThreadLocal对象已被垃圾回收的entity(ThreadLocal作为key,并且继承自WeakReference).而且策略为只扫描 log2(table.length)-1 次.看注释说这样做不仅简单,快速,而且据说效果不错. :-)

@param n scan control: log2(n) cells are scanned, unless a stale entry is found, in which case 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.)

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

get过程

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }

和set()一样,也是先获取当前线程所持有的所有本地变量,然后获取当前ThreadLocal对象在这个线程中的本地变量. 如果Thread的本地变量集合为空,会有一个初始化的过程,在setInitialValue()中会为当前线程新建一个集合,并调用 protected T initialValue()获取当前ThreadLocal的初始值(声明为protected,可由子类指定初始值).

    private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }

InheritableThreadLocal

InheritableThreadLocalThreadLocal类似,也是保存线程本地变量的, 差别是 InheritableThreadLocal中的变量可从父线程中继承过来, 即在创建新线程时,会将当前线程中的InheritableThreadLocal中的所有变量,复制一份到新创建线程的 inheritableThreadLocals中去.所以如果修改父线程中的变量,是不影响子线程中的本地变量的复制代码如下:

    /**
             * Construct a new map including all Inheritable ThreadLocals
             * from given parent map. Called only by createInheritedMap.
             *
             * @param parentMap the map associated with parent thread.
             */
            private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];

                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        ThreadLocal key = e.get();
                        if (key != null) {
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            int h = key.threadLocalHashCode & (len - 1);
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }