ThreadLocal详解

    科技2023-09-28  86

    基本概念

    ThreadLocal的基本作用就是数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是隔离的。

    隔离用在哪里?

    spring实现事务隔离的底层就使用了ThreadLocal。

    保证单个线程中的数据库操作使用的是同一个数据库连接。同时采用这种返回格式可以使业务层使用事务不需要感知并且管理connection对象,通过传播级别,穷啊秒的管理多个事务配置之间的切换 挂起 和 恢复。

    Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面

    你使用过这个吗?(加分项)

    之前我们上线后发现部分用户的日期居然不对了,排查下来是SimpleDataFormat的锅,当时我们使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了。

    其实要解决这个问题很简单,让每个线程都new 一个自己的 SimpleDataFormat就好了,但是1000个线程难道new1000个SimpleDataFormat?

    所以当时我们使用了线程池加上ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程有一个SimpleDataFormat的副本,从而解决了线程安全的问题,也提高了性能。

    ThreadLocal的底层

    ThreadLocal<String> localName = new ThreadLocal(); localName.set("张三"); String name = localName.get(); localName.remove();

    set源码

    public void set(T value) { Thread t = Thread.currentThread();// 获取当前线程 ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象 if (map != null) // 校验对象是否为空 map.set(this, value); // 不为空set else createMap(t, value); // 为空创建一个map对象 }

    大家可以发现set的源码很简单,主要就是ThreadLocalMap我们需要关注一下,而ThreadLocalMap呢是当前线程Thread一个叫threadLocals的变量中获取的。

    每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别人没办法拿到,从而实现了隔离。

    ThreadLocalMap底层结构

    数组 不是hashmap

    用数组是因为,我们开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里,所以肯定要数组来存。

    ThreadLocal的内存泄露问题

    ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。

    这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

    就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。

    按照道理一个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了。

    解决方案

    在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。

    Processed: 0.010, SQL: 8