ThreadLocal

ThreadLocal就是提供给每个线程操作变量的工具类,做到了线程之间的变量隔离目的。
ThreadLocal类用来设置线程私有变量,本身不储存值,主要提供自身引用和操作ThreadLocalMap属性值的方法,使用ThreadLocal会通过ThreadLocal的引用定位到到堆中Thread的类ThreadLocalMap里散列表里的值 从而达到线程私有。

ThreadLocal的原理和使用
每个Thread对象都有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值。
ThreadLocalMap由一个个Entry对象构成。
Entry继承自WeakReference<ThreadLocal<?>>,一个Entry由ThreadLocal对象和Object构成。由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。

当执行threadLocal的set方法时,ThreadLocal首先会获取当前线程对象(Thread t = Thread.currentThread()),然后获取当前线程的ThreadLocalMap对象(t.threadLocals),再以当前ThreadLocal对象为key,将值存至ThreadLocalMap对象中。
get方法执行过程类似,ThreadLocal首先会获取当前线程对象(Thread t = Thread.currentThread()),然后获取当前线程的ThreadLocalMap对象(t.threadLocals),再以当前ThreadLocal对象为key,获取对应的value。

由于每一个线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多线程访问容器的互斥性。

使用场景:
1、线程间数据隔离
2、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

Spring框架在事务开始时会给当前线程绑定一个jdbc Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用ThreadLocal来实现这种隔离。

ThreadLocal内存泄漏原因,如何避免
内存泄漏是程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光。
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。

强引用:使用最普遍的引用(new)、反射,一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。
如果想取消引用和某个对象之间的关联,可以显示的将引用复制为null,这个可以使jvm在合适的时间就会回收该对象。
弱引用:jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。
ThreadLocal在没有外部对象强引用时,如:Thread,发生GC时弱引用Key会被回收,而Value是强引用,只有thread线程退出后才会被回收,但如果创建ThreadLocal的线程一直持续运行,如:线程池中的线程,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

key如果使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set,get,remove的时候会被清除value值。
Java8中已经做了一些优化如,在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLoacalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
ThreadLocal正确的使用方法
每次使用完ThreadLocal都调用它的remove()方法清除数据
将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。

关于 ThreadLocal 是否会引起内存泄漏也是一个比较有争议性的问题,其实就是要看对内存泄漏的准确定义是什么。认为 ThreadLocal 会引起内存泄漏的说法是因为如果一个 ThreadLocal 对象被回收了,我们往里面放的 value 对于【 当前线程->当前线程的 threadLocals (ThreadLocal.ThreadLocalMap 对象)-> Entry 数组 -> 某个 entry.value】这样一条强引用链是可达的,因此 value 不会被回收。

认为 ThreadLocal 不会引起内存泄漏的说法是因为 ThreadLocal.ThreadLocalMap 源码实现中自带一套自我清理的机制。之所以有关于内存泄露的讨论是因为在有线程复用如线程池的场景中,一个线程的寿命很长,大对象长期不被回收影响系统运行效率与安全。如果线程不会复用,用完即销毁了也不会有 ThreadLocal 引发内存泄露的问题。《Effective Java》 一书中的第6条对这种内存泄露称为 unintentional object retention (无意识的对象保留)。

当我们仔细读过 ThreadLocalMap 的源码,我们可以推断,如果在使用的 ThreadLocal 的过程中,显式地进行remove是个很好的编码习惯,这样是不会引起内存泄漏。那么如果没有显式地进行remove呢?只能说如果对应线程之后调用ThreadLocal 的 get 和 set 方法都有很高的概率会顺便清理掉无效对象,断开value强引用,从而大对象被收集器回收。但无论如何,我们应该考虑到何时调用 ThreadLocal 的 remove 方法。一个比较熟悉的场景就是对于一个请求一个线程的 server 如 tomcat ,在代码中对 web api 作一个切面,存放一些如用户名等用户信息,在连接点方法结束后,再显式调用 remove。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值