美文网首页
ThreadLocal介绍

ThreadLocal介绍

作者: 竖起大拇指 | 来源:发表于2020-07-29 13:50 被阅读0次

    ThreadLocal,顾名思义,其存储的内容是线程本地的,私有的,我们常用来存储和维护一些资源或者变量,以避免线程争用或者同步问题。
    用法:

    class ThreadLocalDemo :Runnable {
        var localHolder=object:ThreadLocal<String>(){
            override fun initialValue(): String? {
                return "hello threadLocal"
            }
        }
    
        var localHodler2=object :ThreadLocal<Int>(){
            override fun initialValue(): Int? {
                return 100
            }
        }
    
        
        override fun run() {
           println(localHolder.get())
            println(localHodler2.get())
        }
    
    }
    

    ThreadLocal内存模型

    我们使用ThreadLocal实列提供的set/get方法来存储和使用value,但是ThreadLocal实例其实只是一个引用,真正存储值的是一个Map,其key是ThreadLocal实例本身,value是我们设置的值,分布在堆区,这个Map的类型是Thread.ThreadLocalMap.

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

    当我们重新init或者调用set/get的时候,应该是将value存储到了ThreadLocalMap中,或者从已有的ThreadLocalMap中获取value。

    ThreadLocal.set(T value)
    /**
         * 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);
        }
    
    

    可以看到,getMap就是返回了当前Thread实例的ThreadLocalMap属性.

    public
    class Thread implements Runnable {
      //省略其他代码
     /* 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;
    }
    
    ThreadLocal.get()

    get是获取当前线程的对应的私有变量,是我们之前set或者通过initValue指定的变量 .

     /**
         * 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();
        }
    
     /**
         * 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;
        }
    
    
    
    • 获取当前线程的ThreadLocalMap实例
    • 如果不为空,以当前ThreadLocal实例为key获取value
    • 如果ThreadLocalMap为空或者根据当前ThreadLocal实例获取的value为空,则执行setInitialValue()方法.
      setInitialValue()内部如下:
    • 调用我们重写的initialValue得到一个value
    • 将value放入到当前线程对应的ThreadLocalMap中
    • 如果Map为空 先实例化一个Map,然后赋值KV
    总结
    • 每个线程,是一个Thread实例,其内部拥有一个名为threadLocals的实例成员,其类型是ThreadLocal.ThreadLocalMap
    • 通过实例化ThreadLocal实例,我们可以对当前运行的线程设置一些线程私有的变量,通过调用ThreadLocal的set和get方法存取
    • ThreadLocal本身并不是一个容器,我们存取的value实际上存储在ThreadLocalMap中,ThreadLocal只是作为ThreadLocalMap的key
    • 每个线程实例都对应一个ThreadLocalMap实例,我们可以在同一个线程里实例化很多个ThreadLocal来存储很多中类型的值,这些ThreadLocal实例分别作为key,对应各自的value
    • 当调用ThreadLocal的set/get进行赋值/取值 操作时,首先获取当前线程的ThreadLocalMap实例,然后就像操作一个普通的map一样,进行put和get

    可能存在的内存泄露分析

    由于ThreadLocalMap是以弱引用的方式引用着ThreadLocal,换句话说,就是ThreadLocal是被ThreadLocalMap以弱引用的方式关联着,因此如果ThreadLocal没有被ThreadLocalMap以外的对象引用,则在下一次GC的时候,ThreadLocal实例就会被回收,那么此时ThreadLocalMap里的一组KV的K就是null了,因此在没有额外操作的情况下,此处的V便不会被外部访问到,而且只要Thread实例一直存在,Thread实例就强引用着ThreadLocalMap,因此ThreadLocalMap就不会被回收,那么这里K为null的V就一直占用着内存。
    综上,发生内存泄漏的条件是

    1. ThreadLocal 实例没有被外部强引用,比如我们假设在提交到线程池的task中实例化的ThreadLocal对象,当task结束时,ThreadLocal的强引用也就结束了

    2. ThreadLocal实例被回收,但是在ThreadLocalMap中的V没有被任何清理机制有效清理

    3. 当前Thread实例一直存在,则会一直强引用着ThreadLocalMap,也就是说ThreadLocalMap也不会被GC。
      也就是说,如果Thread实例还在,但是ThreadLocal实例却不在了,则ThreadLocal实例作为key所以关联的Value无法被外部访问,却还被强引用着,因此会出现内存泄漏。

    ThreadLocal本身的优化

    进一步分析ThreadLocalMap的代码,可以发现ThreadLocalMap内部也是坐了一定的优化的。

      /**
             * 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;
                //用key的hashCode计算槽位
                int i = key.threadLocalHashCode & (len-1);
                //hash冲突时,使用开放地址法
                //因为独特和hash算法,导致hash冲突很少,              一般不会走进这个for循环
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {  //key相同 则覆盖value
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {  //key 为null 说明key已经被回收了,进入替换方法
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
                //新增Entry
                tab[i] = new Entry(key, value);
                int sz = ++size;
            //清除一些过期的值,并判断是否需要扩容
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();//扩容
            }
    

    可以看到,在set值的时候,有一定的几率会执行replaceStaleEntry(key,value,i)方法,其作用就是将当前的值替换掉以前的key为null的值,重复利用了空间。
    这个set方法涵盖了很多关键点:

    1.开放地址法:与我们常用的Map不同,java里大部分Map都是链表法解决hash冲突的,而ThreadLocalMap采用的是开放地址法
    2.hash算法:均匀的hash算法使其可以很好的配合开放地址法使用;

    ThreadLocal使用建议
    • 当需要使用线程私有变量的时候,可以考虑使用ThreadLocal来实现
    • 当需要实现线程安全的变量时,可以考虑使用ThreadLocal来实现
    • 当需要减少线程资源竞争的时候,可以考虑使用ThreadLocal来实现
    • 注意Thread实例和ThreadLocal实例的生存周期,因为它们直接关联着存储数据的生命周期
    • 如果频繁的在线程中 new ThreadLocal对象,在使用结束时,最好调用ThreadLocal.remove来释放其value的引用,避免在ThreadLocal被回收时value无法被访问却又占用着内存

    相关文章

      网友评论

          本文标题:ThreadLocal介绍

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