ThreadLocal深度解析

首先从threadlocal的变量开始说起
    //后面在计算线程中缓存元素的位置时会用到哈希值
    private final int threadLocalHashCode = nextHashCode();
    //哈希值默认从0开始
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    //哈希值每次的增量,至于为什么是这个值,看文章最后面
    private static final int HASH_INCREMENT = 0x61c88647;
    //线程中下一个元素存储的哈希值
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
接下来是threadlocal内部的get()方法
/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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 the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

这里第一行,Thread.currentThread();首先拿到当前线程,然后从当前线程中拿到ThreadLocalMap,论证了一个事实,线程的缓存是存储在自身线程的ThreadLocalMap中的。
然后map不为空,map中拿到对应的entry,继而拿到对应的value值,否则初始化默认值。下面先开始讲解初始化默认值的过程。

/**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }
    /**
     * 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);
    }
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

从上述代码可看到,通过ThreadLocalMap构造函数初始化了一个map,并且初始化默认值是null,在实际使用过程中,initialValue()方法可重写,设置适合自己实际情况的默认值。下面让我们看看这个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.
         */
        //首先内部定义了静态实体Entry,继承了弱引用,这样做的原因是当entry中经常存储的是一个对象,如果不使用的话,将值设置为null,这样就没有对象引用他,可以尽快的被jvm回收掉。
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //默认初始化数组大小16
        private static final int INITIAL_CAPACITY = 16;

        //数组,大小默认2的幂
        private Entry[] table;

        //数组的长度
        private int size = 0;

        //数组的最大长度,超过这个要扩容,
        private int threshold; // Default to 0

        //设置上面那个值,最大不超过数组长度的2/3
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }
        //重点来了,构造函数,从前面调用中看到firstKey传递是this,也就是当前threadlocal对象,firstValue默认是null
        构造函数中初始化了table数组,
        计算了一个哈希值和数组长度进行与运算,保证数组的下标在数组中
        初始化了数组长度为1,设置了扩容界限
        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);
        }

        //从数组中获取entry对象,计算哈希值,拿到数组下标,取值,如果没拿到,说明产生了哈希碰撞
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
        //i表示上面计算出来的哈希值,e表示table[i]
        //首先重复校验下判断是否k与key值相等,相等说明找到直接返回
        //中间发现key是空的,调用expungeStaleEntry(i)方法进行内存回收
        //否则进行数组下标移动继续查找
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
        //获取数组下一个下标,上面方法中使用到了
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
        //下面开始set方法
        //首先通过hash值与数组长度计算得到数组下标
        //然后进入for循环,判断数组下标的位置元素不为null,说明数组中本身是有元素的,如果数组存储的key相同,直接赋值返回,不相同情况下如果key是空的,则把值替换赋值进去
        //hash值下标位置元素是null,则把元素放过去,长度+1,对数组进行内存回收没用的元素成功后,如果数组长度不够,进行rehash扩容
        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();
        }
        //上面set过程中调用,从数组下标开始逐个往后进行内存回收,注意回收条件是数组长度大于1(无符号右移1位)
        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;
        }
        //回收某个元素的方法
        //前面几行比较简单,直接把元素的值设置空,元素也设置为空
        //后面有两个过程,一是逐个遍历后面数组元素,判断有空的直接进行回收;二是重新计算元素的哈希值,如果跟以前的不一致重新放个位置
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
    }
    //下面开始rehash的过程,首先对数组进行无用元素的回收,然后判断数组长度是否大于存储上限的3/4,前面threshold最大不超过数组长度的2/3,根据注释是做了双重判断防止产生过多的hash碰撞,这样不超过数组长度的2/3*(3/4)=1/2就能扩容
    private void rehash() {
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }

        //开始扩容,声明数组长度变为2倍,然后对每个元素重新计算hash值放到新的位置
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (Entry e : oldTab) {
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }
        //对数组进行回收
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }

总结:从上面整体ThreadLocalMap的实现中,我们得到一些结论,
1)set方法中hash碰撞后数字直接会在数组中后移一位
2)rehash默认不超过数组的2/3, 第二重判断不超过最大存储的3/4,在rehash函数中不超过数组的1/2就能扩容了
3)在get和set方法过程中会不断判断数组中的元素是否已经没有使用了从而促进内存快速回收

最后说说里面计算hash的时候为什么是0x61c88647

0x61c88647换算成十进制是1640531527,计算方法如下
黄金分割数0.618

16进制10进制2进制
0x61c88647164053152701100001110010001000011001000111
10011110001101110111100110111001(取反+1)

10011110001101110111100110111001转换成十进制后值为2654435769,这个数是一个斐波那契乘数,它的优点是通过它散列出来的结果分布会比较均匀,可以很大程度上避免hash冲突。

位数(w)乘数备注
16405030.6180339887*2^16=40503.4754834432
3226544357690.6180339887*2^32=2654435769.2829335552
64114007148193231984850.6180339887*2^64=11400714818402800990.5250107392

至于更深层的原因我还没查到,欢迎指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值