解锁面试必问题:ThreadLocal 正确姿势

ThreadLocal 百度上面对于他的解释已经非常非常多了,但是很多其实都是复制来复制去,并且很多都是说的错的,最离谱的是百度上面头几页说ThreadLocal在Set的时候内部的ThreadLocalMap存放的Key是当前线程对象...,真的简直在虾扯蛋,不知道误导了多少人

ThreadLocal的API非常简单,这里就不在描述了,主要针对他源码以及设计思路来分析设计者的用意,要了解ThreadLocal原理那么先要了解Thread和ThreadLocalMap是啥...

Thread是Java种的线程类,其中有一段这样的代码,从这段代码的注释种可以看出来这个属性是给ThreadLocal 来使用的,那么具体如何使用的下文会慢慢解析

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal中的内部类,它类似我们所知的常规HashMap,但是仅仅是数据结构类似里面的实现和常规HashMap完全不一样

        /**
         * 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;
            }
        }

从注释和源码可以发现,这个ThreadLocalMap的Entry继承弱引用(软引用、弱引用、虚引用这个可以自行百度,不在这里展开了),ThreadLocalMap的key存放的是ThreadLocal对象,而这个ThreadLocal对象是弱引用,为什么要这么做,下文也会慢慢解释

 

 

看到这里很多同学可能会好奇第一个问题,ThreadLocal是怎么做到线程本地变量隔离的?

这个其实利用Java的栈来做到的,当变量是局部变量时,这个变量的申请空间就会在当前线程的执行栈中,其他线程是无法访问这个变量的,这个就用到了上面的Thread类中的属性,每一个Thread内部都维护了一个自己的threadLocals变量,而这个threadLocals变量的赋值通过了ThreadLocal的内部类threadLocalsMap

 

看到这里很多同学可能会好奇第二个问题,ThreadLocalMap为什么要用弱引用?

public void func1() {
        ThreadLocal tl = new ThreadLocal<Integer>(); //line1
         tl.set(100);   //line2
         tl.get();       //line3
}

line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象;line2调用set()后,新建一个Entry,通过源码可知entry对象里的 k是弱引用指向这个对象。如图:

当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏,但是弱引用就不会有这个问题(弱引用及强引用等这里不说了)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且在entry的k引用为null后,再调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

概括说就是:在方法中新建一个ThreadLocal对象,就有一个强引用指向它,在调用set()后,线程的ThreadLocalMap对象里的Entry对象又有一个引用 k 指向它。如果后面这个引用 k 是强引用就会使方法执行完,栈帧中的强引用销毁了,对象还不能回收,造成严重的内存泄露。

注意:虽然弱引用,保证了k指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现k为null时才会去回收整个entry、value,因此弱引用不能保证内存完全不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

 

 

看到这里很多同学可能会好奇第三个问题,同一个线程内部申明了多个不同的ThreadLocal,那么ThreadLocalsMap会如何处理?

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

可以看到ThreadLocal在set的时候,map的key是当前ThreadLocal对象,并且这个对象是一个弱引用,当同一个线程内部使用了多个ThreadLocal时,如果发生了Hash冲突,ThreadLocalMap会直接利用Entry下标+1的方式往后走,直到Hash冲突消失(这种方式称为:拉链法

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝-琪琪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值