主要作用
数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的
原理
线程中创建副本,访问自己内部的副本变量,内部实现是其内部类名叫ThreadLocalMap的【成员变量threadLocals】,key为本身,value为实际存值的变量副本
ThreadLocalMap 是一个定制化的HashMap,为什么是个HashMap?很好理解,每个线程可以关联多个ThreadLocal变量。
ThreadLocalMap 初始化时会创建一个大小为16的Entry 数组,Entry 对象也是用来保存 key- value 键值对(这个Key固定是ThreadLocal 类型)。
值得注意的是,这个Entry 继承了 WeakReference(这个设计是为了防止内存泄漏)。但是,Entry中的value是强引用,不易被回收
底层实现原理 ThreadLocalMap
ThreadLocal数据隔离的真相了,每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别人没办法拿到,从而实现了隔离
副作用
会出现内存泄漏,显式remove..不要与线程池配合,因为worker往往是不会退出的
内存泄露问题,针对的是threadLocal对应的Value对象实例。在线程对象被重用且threadLocal为静态变量时,如果没有手动remove(),就可能会造成内存泄露的情况
解决办法:在使用结束时,调用ThreadLocal.remove来释放其value的引用;为什么需要手动remove?
ThreadLocal对象通常作为私有静态变量使用(如果说一个 ThreadLocal 是非静态的,属于某个线程实例类,那就失去了线程内共享的本质属性),作为静态变量使用的话,
那么其生命周期至少不会随着线程结束而结束。也就是说,绝大多数的静态threadLocal对象都不会被置为null。这样子的话,通过 stale entry 这种机制来清除Value对象实例这条路是走不通的。
必须要手动remove() 才能保证。脏数据: 如果在线程池中的线程使用了ThreadLocal对象,因为线程池会复用线程,所以自然而然会产生脏数据
以上两个问题通常是在线程池的线程中使用 ThreadLocal 引发的,因为线程池有【线程复用】和【内存常驻】两个特点
如果我们要获取父线程的ThreadLocal值呢
ThreadLocal是不具备继承性的,所以是无法获取到的,但是我们可以用InheritableThreadLocal来实现这个功能。InheritableThreadLocal继承来ThreadLocal,
重写了createdMap方法,已经对应的get和set方法,不是在利用了threadLocals,而是inheritableThreadLocals变量。(应用场景: 用一个统一的ID来追踪记录调用链路)
有一种场景,InheritableThreadLocal 无法解决,也就是在使用线程池等会池化复用线程的执行组件情况下,异步执行执行任务,需要传递上下文的情况。针对这种情况,阿里开源了一个库来解决,
可以看一下https://github.com/alibaba/transmittable-thread-local
自己在业务中的使用场景
ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,即变量在线程间隔离而在方法或类间共享的场景。
满意度调查邮件发送使用ThreadLocal传递上下文
事务隔离: Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接
用来解决数据库连接,存放connection对象,不同线程存放各自session;
解决simpleDateFormat线程安全问题;