zoukankan      html  css  js  c++  java
  • ThreadLocal中的WeakReference

    在一般的网站开发中,基于Java的Web 框架都使用了ThreadLocal来存储一些全局的参数,在拦截器Filter中设置变量,让变量可以在任意地方被获取。

    一早就了解到里面有用到WeakReference(弱引用),但对弱引用仅限于一种懵懂的概念,并且认为只要GC,弱引用的对象就被回收掉了,实际情况呢?

    Thread对象有一个变量名为 threadLocals 的 ThreadLocalMap对象,这个类和HashMap类似,里面定义了一个Entry数组,不过这个Entry对象弱引用了ThreadLocal

    /**
             * 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);    //调用父类构造函数,设置Reference的referent属性
                    value = v;
                }
            }
    /**
             * 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();     获取Reference的referent属性
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);   //清理掉陈旧Entry,这种Entry是由于ThreadLocal变量对象被回收后k==null造成的
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

    注意以下方法

                    if (k == null) {
                        replaceStaleEntry(key, value, i);   //清理掉陈旧Entry,这种Entry是由于ThreadLocal变量对象被回收后k==null造成的
                        return;
                    }

    这个地方其实也解释了通常所说的ThreadLocal变量可能导致内存溢出的问题:

     Entry弱引用了ThreadLocal,因此Entry是否存活,不会影响ThreadLocal的生命周期,ThreadLocal在没有其他对象引用后被回收,但是它对应的Entry中的value实际还在被Entry引用,而这不是一个弱引用,如果不清掉掉Entry,value就被一直强引用,无法释放,那么就可能会内存溢出

    如果一直没有ThreadLocal变量访问,并且线程一直存活,就不会清理陈旧Entry,value永远无法释放,这也是某种意义上的内存泄露

    一直以来,我把WeakReference理解成:弱引用的对象一定会在下次GC时回收掉,按此推断,ThreadLocal变量是不太安全的,因为用着用着就可能被GC掉了。

    但是如果ThreadLocal有这么严重的问题,谁会去用呢!

    实际上对象B弱引用了A,如果A除了B以外,没有其他引用(强、软引用)时,才会把A GC调,这也是为什么ThreadLocal实际上是一个安全的操作。

    Thread.threadLocals 是一个Map,每个Entry都弱引用了ThreadLocal对象
    因此Thread.threadLocals对每个ThreadLocal对象都是弱引用关系

    调用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);   可以看到get实际上是获取当前线程的ThreadLocalMap
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            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;
    }

    可以理解为一个ThreadLocal实例,在多个线程都存在副本,并且在不同线程设置的值,都不会影响到其他线程,因为ThreadLocal实例是存储在当前Thread对象上的

    也就是说,如果Thread死掉后,它和ThreadLocal对象也就没有任何关系了,这对我们的程序没有任何影响,因为我们也没有机会再取它在Thread中的副本了;

    而ThreadLocal变量被设置为null后,虽然Thread中的Entry弱引用了ThreadLocal,即使Thread对象还在,当ThreadLocal对象没有被其他对象引用后,它就可以被GC调了,Thread对象产生了弱引用,不会影响到ThreadLocal变量的回收

    其他知识点:

    需要注意的是,当一个线程执行Thread.start()后,如果start()方法已经执行完毕了,虽然Thread对象还在,其实它的生命周期已经结束了,对应的ThreadLocal变量可以被回收

    如果线程再次调用Thread.start()方法,会抛出异常,因此不用关注线程再次被启动,因为它已经无法再被启动了(PS:线程复用技术并非是重新start,而是在线程内执行多个任务)

    以下情况ThreadLocal变量是永远不会回收的

    public class FlagContext {
    
        /**
         * ThreadLocal变量
         */
        private final static ThreadLocal<Boolean> tbFlag = new ThreadLocal<Boolean>();
    }

    FlagContext的静态变量引用了ThreadLocal实例tbFlag,tbFlag是个final 声明的变量,永远不会为空,因此,这种情况下,tbFlag对应的值,只会在线程生命周期结束,或调用tbFlag.remove()才会被回收!原因呢:

     ThreadLocal永远被FlagContext强引用了,而Thread和ThreadLocal不管是否是弱引用,即使Thread中的Entry被回收了,也不会被GC掉!

  • 相关阅读:
    数仓备机DN重建:快速修复你的数仓DN单点故障
    深度学习分类任务常用评估指标
    云小课 | MRS基础入门之HDFS组件介绍
    华为云数据库GaussDB(for Cassandra)揭秘第二期:内存异常增长的排查经历
    为什么vacuum后表还是继续膨胀?
    Go 自定义日志库
    Go time包
    Go 文件操作
    Go 包相关
    【程序人生】跟小伙伴们聊聊我有趣的大学生活和我那两个好基友!
  • 原文地址:https://www.cnblogs.com/windliu/p/7623369.html
Copyright © 2011-2022 走看看