ThreadLocalMap数据结构
与其他map相类似,ThreadLocalMap内部存储的数个格式也是<k, v>形式的。不同点在于,ThreadLocalMap的key设置的当前线程,value设置的是继承自弱引用类型的WeakReference的Entry对象。
通过阅读源码,不难发现。ThreaLocalMap在使用的时候。其实都是在方法内部,获取到了本地线程,进行操作的。
ThreadLocalMap整体的数据结构可以理解为一个只能存放单个key对象的map集合。
ThreadLocal使用场景
ThreadLocal的使用场景,其实比较容易。首先得知道ThreadLocal得特性才行。
特性比如:
- 线程私有,不可共享
- 只能存放单个值对象
所以总体而言,一般这个容器,可以用来存放仅供当前线程使用,但又不方便(或者没必要)进行重复值传递得对象。
比如说: - 已有得系统上,需要接入多租户信息,为了达到数据操作时,有数据级别的隔离。这时候,我们要么在原有系统上,进行大刀阔斧得,每个查询、新增、修改方法都需要加上租户。不如直接使用spring得aop特性。在接口得初始化调用时,存放到统一得map中,在sql查询时拦截sql进行拼接。
ThreadLocal产生内存泄漏的原因
首先内存泄漏是什么东西呢?
内存泄漏是程序在申请内存后,无法释放已申请的内存空间,多次内存泄漏就会导致内存耗光的严重问题。
在这里就有个概念了,在Java中存在多种应用对象。
强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间回收该对象。
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
我们的ThreadLocal存放得entry就是被WeakReference标记得弱引用类型对象。
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreadLocal)就会被GC回收,这样就会导致ThreadLocalMap中key为null,而value还存在强引用,只用thread线程退出,value的强引用链条才会断掉,但如果当前线程未结束,这些key为null的Entry的value就会一直存。
如果ThreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal就不会被回收,导致Entry内存泄漏。
ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()时会清除value的值。
因此,ThreadLocal内存泄漏的根源是由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不因为弱引用。
如何避免ThreadLocal产生的内存泄漏
ThreadLocal正确的使用方法:
- 每次使用完ThreadLocal都要调用它的remove()清除数据。
- 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。