ThreadLocal浅谈

ThreadLocal其实理解为Thread的本地局部变量更贴切。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal 的作用是提供线程内的局部变量(成员变量不能·实现线程安全),这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
ThreadLocal 其作用为每个线程的局部变量提供一个副本,多个线程之间互相不影响,实现了线程局部变量之间的隔离性。在同一个线程中任意位置可以直接get到这些变量,具有线程内共享性。
那么ThreadLocal 在spring有何应用呢?

典型的应用就是事务回滚:在事务管理中,在service类中的涉及到事务的方法,每个事务的上下文都应该是独立拥有数据库的connection连接的,否则在数据提交回滚过程中就会产生冲突。使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。

ThreadLocal怎么实现线程内部变量的隔离和共享呢?
首先看下全部方法和内部类
在这里插入图片描述

一般我们平时使用的时候关心的只是get,set,remove方法
我们从这三个方法入手看下ThreadLocal 的源码分析

set方法

将此线程局部变量的当前线程副本设置为指定值。大多数子类不需要重写此方法,仅依靠 initialValue 方法来设置线程局部变量的值。 Params: 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);
    }

getMap方法返回ThreadLocalMap传入参数t当前线程,继续跟进

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到ThreadLocalMap 其实是Thread类的threadLocals成员变量,当map存在可以理解为将当前ThreadLocal作为键,value为值set进map中;不存在时createMap初始化

创建与 ThreadLocal 关联的映射。在 InheritableThreadLocal 中重写。参数: t - 当前线程 firstValue - 映射初始条目的值
 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
构造一个最初包含 (firstKey, firstValue) 的新映射。 ThreadLocalMaps 是惰性构建的,所以我们只有在至少有一个条目可以放入时才创建一个。
 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
         table = new Entry[INITIAL_CAPACITY];
         int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
         table[i] = new Entry(firstKey, firstValue);
         size = 1;
         setThreshold(INITIAL_CAPACITY);
     }

可以看出ThreadLocalMap这个map的实现是使用一个数组 private Entry[] table 来保存键值对的实体(Entry实体,两个属性ThreadLocal和传过来的value值),初始大小为16,ThreadLocalMap自己实现了如何从 key 到 value 的映射:int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)。
看到这里可以得出Thread,ThreadLocalMap,ThreadLocal的对应关系:一个Thread只有一个ThreadLocalMap,一个ThreadLocalMap可以对应多个ThreadLocal。如果还是有疑问我们可以看下面一段代码:

 public static void main(String[] args) {
        ThreadLocal<String> threadLocal=new ThreadLocal<>();
        ThreadLocal<String> threadLoca2=new ThreadLocal<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("123");
                threadLoca2.set("456");
                System.out.println(Thread.currentThread().getName());
            }
        },"Theead1").start();
    }

打断点看下运行结果
在这里插入图片描述
get方法

返回此线程局部变量的当前线程副本中的值。如果变量没有当前线程的值,则首先将其初始化为调用 initialValue 方法返回的值。返回:此线程本地的当前线程的值
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();
    }

和set方法一样根据当前线程t获取ThreadLocalMap ,然后根据使用的当前的ThreadLocal找到数组下标获取Entry 对象,然后取出变量值有兴趣的哥们可以自己跟下

remove方法

删除此线程局部变量的当前线程值。如果这个线程局部变量随后被当前线程读取,它的值将通过调用它的 initialValue 方法重新初始化,除非它的值是由当前线程在中间设置的。这可能会导致在当前线程中多次调用 initialValue 方法
 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

看完set和get方法remove这里就很简单了,不再赘述。

ThreadLocal可能引起的OOM内存溢出问题
为了说明这问题在这里引用一张图
在这里插入图片描述
对value的引用线路有两条:

  1. threadlocalref 是ThreadLocal强引用,key是ThreadLocal变量的弱引用。由于key是弱引用,当ThreadLocalRef因不用而释放掉的时候,ThreadLocal对象就会被回收,由于是key到threadLocal对象为弱引用,一旦进行垃圾回收key就会被回收而相应位置变为null,当然value依然存在
  2. 通过当前线程的引用可以获取当前线程对象,当前线程对象就可以获取到ThreadLocalMap,那么只要当前线程一直存在,ThreadLocalMap对象就会一直存在。

由于ThreadlocalMap存活时间和线程一样,比如我们采用的是常驻线程池,使用线程过程中没有清空ThreadLocalMap,也没有调用threadlocal的remove方法,就将线程放回线程池,虽然ThreadLocal的强引用ThreadLocalRef被清除,弱引用key在GC的时候也会被设置为null,但是对于value值还存在一条强引用链条所以value并没有释放,就造成了内存泄漏了。
那这时候你或许会问为啥ThreadLocalMap存储value的时候不采用弱引用呢?这样不就可以避免内存泄漏了么?value是弱引用是不行的,原因很简单:我们存储的对象除了ThreadLocalMap的Value就没有其他的引用了,value一但是对象的弱引用,GC的时候被回收,对象就无法访问了,这显然不是我们想要的。

如何避免呢?
为避免内存泄漏最好在使用完ThreadLocal之后调用其remove方法,将数据清除掉。
当然,对于Java8 ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏
get方法会间接调用expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值