美文网首页
ThreadLocal

ThreadLocal

作者: zoooooyyyy | 来源:发表于2020-08-20 21:05 被阅读0次

    使用方法

    ThreadLocal可以存储线程的本地属性,可以用来做线程相关的数据缓存以及保存traceId等。但是由于现在线程基本由线程池创建,所以使用ThreadLocal的时候会出现内容与预期不符的情况,这是因为线程池内的线程是复用的,解决方案是使用完毕就调用threadLocal.remove()方法

    源码分析

    ThreadLocal内有一个静态内部类ThreadLocalMap。实际上,ThreadLocalMap才是数据真正存储的地方,ThreadLocal仅仅是一个工具类,提供了可操作ThreadLocalMap的静态方法。


    image.png

    ThreadLocalMap

    ThreadLocalMap 中有一个静态内部类Entry,弱引用了ThreadLocal,同时存储了Object类型的value值

    弱引用: 当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。

    ThreadLocalMap 内部维护了一个Entry数组table,数组的初始化大小为16,默认装载因子为总容量的2/3。ThreadLocalMap 自己的hash算法给每个ThreadLocal计算出一个hash值i,然后把对应的value存储在数组的table[i]位置。
    hash值重复的怎么办?
    当计算出的hash值已存在时,会对计算出的hash值再次进行rehash计算,直到找到一个未被占用的位置为止。
    数组容量不够了怎么办?
    当数组的数量大于总容量的2/3时,会进行脏key的回收(软引用对象被回收的entry),回收后数量>=总容量的1/2则进行扩容,扩容后的容量为原来的2倍大,并且需要对所有的值进行rehash(hash函数和数组容量有关)

    看完ThreadLocalMap我们回头看看ThreadLocal到底持有什么东西

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

    ThreadLocal有一个不可变的成员变量threadLocalHashCode(可能重复),值由全局的AtomicInteger 生成。HASH_INCREMENT 是为了让哈希码能均匀的分布在2的N次方的数组里。

    真令人惊讶,ThreadLocal中不存储任何和线程有关的属性,那么这个属性到底存储在哪里?
    答案是存储在Thread中。

        /* 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 和inheritableThreadLocals ,数据类型都是ThreadLocal.ThreadLocalMap。

    每当在线程下创建一个ThreadLocal,就会给ThreadLocal生成一个threadLocalHashCode。

        public T get() {
            Thread t = Thread.currentThread();
            // return t.threadLocals;
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 根据this的threadLocalHashCode计算出下标,然后从数组中获取value
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
    

    当调用ThreadLocal的set方法时,会把相应的值存储到当前线程的threadLocals 中,可以理解为key=ThreadLocal value=value。调用get方法查询时,也会根据当前ThreadLocal的threadLocalHashCode值进行hash,然后在当前线程的threadLocals 中获取对应值

    set
    get

    ThreadLocal内存泄漏问题

    由于ThreadLocalMap中的Entry对ThreadLocal是软引用,所有一旦ThreadLocal被垃圾回收后,Entry中的value将会无法被remove(好像扩容的时候会进行清理),从而导致内存泄漏。(只有ThreadLocal被回收了,Thead还没被回收才会发生)

    解决方案:使用ThreadLocal时

    finally{
        threadLocal.remove()
    }
    

    相关文章

      网友评论

          本文标题:ThreadLocal

          本文链接:https://www.haomeiwen.com/subject/mvwejktx.html