ThreadLocal介绍及其原理

ThreadLocal

ThreadLocal可以设置存储属于当前线程的对象,存在当前线程(CurrentThread)的ThreadLocals中,ThreadLocals是ThreadLocalMap类的对象。

ThreadLocal的作用:

存储单个线程上下文信息。比如存储id等;减少参数传递。比如做一个trace工具,能够输出工程从开始到结束的整个一次处理过程中所有的信息,从而方便debug。由于需要在工程各处随时取用,可放入ThreadLocal。

原理
public void set(T value) {//为ThreadLocal对象设置value
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//ThreadLocalMap对象的引用是由当前线程持有,ThreadLocal对象没有map引用!!!!
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
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();//如果map中没有或map为空,则返回初始值,注意初始值可能为null,也可能为之前设置的初始值。要想设置初始值,得使用子类SuppliedThreadLocal,也就是调用ThreadLocal.withInitial()创建对象。
    }
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
            return new SuppliedThreadLocal<>(supplier);
        }

    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }

private static  ThreadLocal<String> INT_OF_THREAD = ThreadLocal.withInitial(() ->"kyrie");
//像上述这样构造ThreadLocal对象,将返回子类SuppliedThreadLocal的对象,使用到了ThreadLocal里面的静态内部类SuppliedThreadLocal。
内存泄漏问题
static class ThreadLocalMap {

        
        static class Entry extends WeakReference<ThreadLocal<?>> {
           
            Object value;

            Entry(ThreadLocal<?> k, Object v) {//map中存放的key是ThreadLocal的弱引用
                super(k);
                value = v;
            }
        }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cCrRLZnp-1586486230724)(C:\Users\kyrie\Pictures\threadlocal.PNG)]

上图是ThreadLocal对象及引用,ThreadLocalMap对象及引用的分布。

map中key使用弱引用的原因

如果使用强引用,当ThreadLocal对象(假设为ThreadLocal@123456)的引用(即:TL_INT,是一个强引用,指向ThreadLocal@123456)被回收了,ThreadLocalMap本身依然还持有ThreadLocal@123456的强引用,如果没有手动删除这个key,则ThreadLocal@123456不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收,可以认为这导致Entry内存泄漏。
如果使用弱引用,那指向ThreadLocal@123456对象的引用就两个:TL_INT强引用,和ThreadLocalMap中Entry的弱引用。一旦TL_INT被回收,则指向ThreadLocal@123456的就只有弱引用了,在下次gc的时候,这个ThreadLocal@123456就会被回收。
key被删了之后,变成了null,value更是无法被访问到了!针对这一问题,ThreadLocalMap类的设计本身已经有了这一问题的解决方案,那就是在每次get()/set()/remove()ThreadLocalMap中的值的时候,会自动清理key为null的value。如此一来,value也能被回收了。

如果面试问为什么不将value也设置为弱引用,方便清除处理?

那是因为在map外部很有可能没有value的强引用,value就可能随时被GC,那就没有使用ThreadLocal的必要了。(key在map外部一般会有一个全局的强引用,和value的情况不一样)

参考文章

原文链接:https://blog.csdn.net/puppylpg/article/details/80433271

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值