ThreadLocal的get方法的源码分析

今天看面经说,ThreadLocal对象调用get方法,会触发对value内存的释放。所以就阅读源码来验证一下。

前言

众所周知,ThreadLocal的实现原理,就是通过在每个线程对象中维护一个map,ThreadLocal每次操作时拿到调用者线程,得到对应map,以自己为key,操作当前线程的资源对象(value)。
其中的map里,维护Entity数组。Entity是一个继承了弱引用的内部类,使用父类构造器将Entity的key设置成了弱引用。
在这里插入图片描述
所以说,如果ThreadLocal对象没有其他强引用,那么在gc时,这个对象会被回收。
但是value不会被回收。

value什么时候被回收?这里有3个情况:

  1. get(key)时,发现对应位置的key是null。这时会释放掉value的内存(这里有一个和一般map不一样的地方,就是如果get(key)发现对应位置的key是空的,就会往这个位置放一个key,然后值是null)
  2. set (key)时,会使用启发式扫描,清除临近的null key的value,启发次数与元素个数,是否发现null key有关。如果元素多,就多扫描,发现了null key就多扫描。
  3. 主动remove key时,会释放value内存。因为在使用ThreadLocal时,一般都作为静态变量,对ThreadLocal对象进行强引用,所以前两种方法一般不会管用,所以最好主动调用remove方法。

这次针对第一种情况展开探索。

正文

首先调用 ThreadLocal 对象的 get 方法

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//如果释放value,肯定是在map中做的,因为map管理着k-v
            ThreadLocalMap.Entry e = map.getEntry(this);//进入这个方法
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

map.getEntry(this) 源码

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //e.get() == key是在干啥?
            //首先Entry继承了弱引用,而弱引用又继承了引用
            //这个get方法是Reference<T>类中的方法
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

e.get() 源码

    /**
     * Returns this reference object's referent.  If this reference object has
     * been cleared, either by the program or by the garbage collector, then
     * this method returns <code>null</code>.
     *
     * @return   The object to which this reference refers, or
     *           <code>null</code> if this reference object has been cleared
     */
    public T get() {
        return this.referent;
    }

注意注释中说的
If this reference object has been cleared, either by the program or by the garbage collector, then this method returns <code>null</code>.
如果这个对象被清理了,那么返回 null。否则返回这个对象

也就是说 e.get() == key在e不为null的情况下有这么几种情况:

  1. 相等,说明找到了正确的Entity,返回e
  2. e.get()不为null,但是也不相等,说明出现了hash冲突
  3. e.get()为null,说明key被清理了

如果是后两种情况,就会进入else分支,那么看一下这个分支下,方法的源码

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                //如果 e 不是null,那么拿到这个 k
                ThreadLocal<?> k = e.get();
                if (k == key)	//如果key相等直接返回e,因为这循环中,可能找到相等的k
                    return e;
                if (k == null)	//如果k是null,说明它被清理了
                    expungeStaleEntry(i);//翻译过来就是,消除不新鲜的实体,可以看看这个方法的实现
                else	//对应hash冲突的情况,那么开放寻址法继续查找
                    i = nextIndex(i, len);
                e = tab[i];
            }
            //如果 e 本来就是 null ,那么说明当前线程中 map 里没有保存过对应的实体,
            //这好说,`setInitialValue()` 把 k 放到map里,v是null。
            return null;
        }

expungeStaleEntry 源码

        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //这里把value 和 entity 都置为null,这样gc的时候就将其清理了
            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            //因为清理了一个位置,所以需要进行重新计算hash,调整位置
            //同时清理一路上的k为null的位置
            // 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;
        }

源码分析到这里就结束了。第一次自己分析集合以外的源码,记录一下

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值