关于Threadlocal的理解

threadlocal

在使用threadlocal的时候其实不是threadlocal绑定线程,而是threadlocal被绑定在线程上,

而且可以有多个threadlocal被绑定在线程上,确切的说是作为key绑定在线程的hash表上!

线程是主导,threadlocal是被绑定

一个线程可以绑定多个threadlocal对象吗?

一个线程可以和很多threadlocal对象建立间接联系,直接和线程联系的是threadlocalMap对象,threadlocalMap 对象和具体的threadlocal对象没有关系(仅仅是他的内部类而已)

thread类中的这个map是以一个个threadlocal对象为key值,以用户的变量为value

所以只要在当前线程作用下创建多个threadlocal实例,那thread对象中的map就会有多个记录,每一个threadlocal对象为key


总结: 一个线程可以绑定多个threadlocal对象,线程真正绑定的是threadlocalMap,map里面会存储很多threadlocal和变量值的key-value

threadlocal的静态方法withInitial()和实例方法set()的异同点

俩个方法都能赋值

withInitial()是个懒加载方法,set()是直接加载

具体体现在set方法会直接判断当前线程的threadlocalMap是否为空,为空则创建之

withInitial()其实是创建一个SuppliedThreadLocal对象,并重写了initialValue()方法

如下:

       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();
        }
    }
withInitial()方法不会给当前线程创建threadlocalMap,只会在用户调用get方法的时候判断thread.map不存在,不存在的话调用SuppliedThreadLocal.initialValue创建threadlocalMap

使用threadlocal 会引起内存泄漏吗

会,但是分场景,手动创建线程,线程用完会销毁的场景不会内存泄漏
              线程池的场景中使用threadlocal会引起内存泄漏

但是内存泄漏不是threadlocal的问题,往threadlocalmap里面的entry赋值的时候key被weakrefrence修饰(即weekreference<threadlocal>作为y被赋值给map里的entry)

引用threadlocal的对象被回收了,则threadlocal在下一次GC时也会被回收,但是entry里的value不会被回收,因为该value绑定entry,绑定threadlocalmap,间接的绑定线程,若线程是线程池里面的线程,则线

程不会销毁, 则entry里面的value也不会被销毁,会导致内存泄漏。

而threadlocal早就被gc回收了。


总结:内存泄漏不是threadlocal对象的问题,是因为threadlocalMap.entry 里的value 和线程是强绑定,线程回收,vaule不回收

如何避免上述弱引用引发的内存泄漏?

在使用完ThreadLocal时,及时调用它的的remove方法清除数据。

remove 方法会调用 expungeStaleEntry(int staleSlot) 方法,清除key为空(key就是threadlocal)的entry

总结: 及时清除threadlocal.map里面的vaule 就可以避免内存泄漏,(ThreadLocal的回收不用担心,weekreference修饰,不用的时候会自动GC)

InheritableThreadLocal

解决父线程传递本地变量到子线程的
https://blog.csdn.net/hewenbo111/article/details/80487252

InheritableThreadLocal 是如何在父子线程间传递变量的

    因为Thread类有2个变量:  ThreadLocal.ThreadLocalMap threadLocals = null; // 线程本地变量
                           ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //继承线程本地变量

    再然后就是线程的构造方法了
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    构造方法里面的init的重载方法

    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {}


    最后一个参数: boolean inheritThreadLocals 表示是否需要从父线程继承 inheritableThreadLocals 属性

    如下判断:

    if (inheritThreadLocals && parent.inheritableThreadLocals != null){  // 如果重载的init方法里的布尔值inheritThreadLocals为true
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//则将当前子线程的inheritableThreadLocals属性设置
//为父线程的inheritableThreadLocals属性,即把父线程额threadlocalMap 赋值给子线程的 inheritableThreadLocals字段
    }
            
    我们知道线程从threadlocal里调用get()方法获取绑定的参数的时候主要是先调用getMap()方法取出对于的threadlocalMap对象

    恰好 InheritableThreadLocal 重写了 getMap() 方法

      ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
 
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

    若实现类是InheritableThreadLocal对象时getMap方法取的是当前线程的ThreadLocal.ThreadLocalMap inheritableThreadLocals 属性,

    而当前线程的该属性已经在构造当前线程对象的时候从父线程里面copy出来了,并且赋值了(详见上面Thread类的重载的init方法)


    总结下来就是:

    当前线程里面调用threadlocal对象的get()方法取值的时候,如果threadlocal对象是ThreadLocal时,则从threadLocals属性取ThreadLocalMap,
                                                        如果threadlocal对象是InheritableThreadLocal时,则从inheritableThreadLocals属性取ThreadLocalMap,

    再从取出的Map里面取值。


    所以能做到父子线程传递变量

TransmittableThreadLocal

解决线程池里面变量传递的问题
https://blog.csdn.net/hewenbo111/article/details/90053105
posted @ 2020-09-25 16:52  划过天空阿忠  阅读(217)  评论(0编辑  收藏  举报