ThreadLocal理解

一、介绍

ThreadLocal,顾名思义,线程本地变量,只属于当前线程,其他线程无法获取这个变量,是隔离的。不同线程虽然有相同的变量名,但是实际地址各不相同。
每个线程都有自己的一个副本。
每个Thread都有一个与之关联的ThreadLocalMap,map中保存了一个键值对Entry,其中键值为ThreadLocal,值是通过Threadlocal.set(value)设置的值,不过Entry指向ThrealdLocal的引用为软引用。当方法中指向ThreadLocal的变量丢失后,entry中维护了一个弱引用指向ThreadLocal对象,当经过一次垃圾回收后,弱引用也被回收,就没有办法获取到value,但是value内存不能回收,产生内存泄漏。

二、源码分析

//获取值
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //从当前线程获取的成员变量是否为空
        if (map != null) {
        	//获取map的键值对
            ThreadLocalMap.Entry e = map.getEntry(this);
            //entry不为空则取entry的value
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //
        return setInitialValue();
    }
    //返回当前线程的ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	//为当前线程创建ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //设置初始值,
    private T setInitialValue() {
    	//设置初始值为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
    //为当前线程设置值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

在这里插入图片描述
1)每个线程都有一个ThreadLocalMap对象;
2)threadLocal.get()方法其实是获取当前线程的threadlocalmap对象,然后以threadlocal为key,通过开放选址法获取map中存储的value;
3)ThreadLocalMap是底层是一个存储Entry的数组,存储threadlocal对象时,先计算threadlocal的hash值,然后查找在Entry数组中的位置,Entry对象保存键值对,对key的保存通过弱引用进行保存,对valu使用强引用保存。Entry类持有ThreadLocal的弱引用,持有ThreadLocal中保存变量的强引用。
4)Threadlocal通过开放寻址法在ThreadLocalMap中查找键值对,FastThreadLocal 获取其中保证的变量值时,使用内部的index变量便可定位对应变量值
5)如果不在线程中进行remove(),当进行垃圾回收时,Entry中对ThreadLocal的弱引用将会导致ThreadLocal被回收,但是Entry中的值是强引用不会被回收,产生内存泄漏。

三、子线程如何获取父线程的ThreadLocal值

可以使用线程的InheritablThreadLocalMap,这个在创建线程对象的时候会复制到子线程中。

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

InheritableThreadLocal也是一个ThreadLocal对象,不过它是存储在Thread的inheritableThreadLocale变量中,在创建对象的时候,会从父类中拷贝一份副本。

四、ThreadLocal的内存泄漏

因为ThreadLocale保存在ThreadLocalMap中,但是他的键值对中key是一个弱引用,在进行垃圾回收时,就会被jvm回收,但是value是一个强引用无法被回收,导致内存泄漏。
当将threadlocal的引用设为null后,ThreadLocal就只有entry的key进行关联,key是一个弱引用,垃圾回收时,就会被干掉。
解决方案:1、在ThreadLocal 设为null前,将key为空的键值对情况。
2、将ThreadLoal设为static
3、非必要不要放大对象在ThreadLocal中
在这里插入图片描述

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

五、使用场景

1、数据库连接、处理数据库事务
每个线程都有一个连接对象副本,不用担心造成线程安全问题,进行回滚或者其他操作,都不会受到干扰。
2、数据跨层传递
通过静态方法传递参数,避免参数传递的麻烦。
3、spring中使用ThreadLocal解决线程安全问题
equestContextHolder、TransactionSynchronizationManager、LocaleContextHolde等可以直接获取ThreadLocal对象。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值