简介
ThreadLocal 是一创建线程局部变量的类,就是说这个类创建的变量值能被当前线程访问,其他线程无法访问和修改。
特点
Global:在当前线程中,任何位置能获取到ThreadLocal的值
Local:该线程的ThreadLocal只能被该线程访问,一般情况下其他线程访问不到(使用InheritableThreadLocal可以将某个线程的ThreadLocal值在其子线程创建时传递过去)。
简单使用
1 | public static void main(String[] args) throws Exception{ |
执行结果可见其特点。在主线程和子线程中变量互不影响。
如何实现
查看set 方法的源码
1 | public void set(T value) { |
看getMap 方法 获取的其实是Thread的 threadLocals变量。
1 | ThreadLocalMap getMap(Thread t) { |
1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
看threadLocals初始化方法为 createMap(Thread t, T firstValue)
1 | void createMap(Thread t, T firstValue) { |
总结,threadLocal 的值是存在当前线程的一个ThreadLocal.ThreadLocalMap 实列中,所以只有当前线程能够访问。
InheritableThreadLocal
使用InheritableThreadLocal子线程可以获取到创建它的父线程的值。
1 | public static void main(String[] args) throws Exception{ |
InheritableThreadLocal如何实现
关键在于InheritableThreadLocal继承了ThreadLocal 复写了getMap,getMap
1 | public class InheritableThreadLocal<T> extends ThreadLocal<T> { |
可以看到使用InheritableThreadLocal存放值的地方变成了线程的inheritableThreadLocals变量而不是 threadLocals变量。
而在线程的初始化方法中可以看到这里会把父线程的值赋值给子线程。
注意:这里是创建线程的时候将父线程的值传递给子线程,若子线程创建完成再修改父线程中的值,不会同步到子线程中去。这里我遇到过坑,后面马上说。
踩坑记录
描述
使用shiro做权限管理框架,用SecurityUtils.getSubject() 获取当前用户登录信息;在线程池中执行用户提交的相关任务,B用户提交的任务,在线程池中获取用户登录信息的时候得到的却是用户A的。
正是由于这个踩到了这个坑,才有了这篇文章
分析与重现
经过源码分析,SecurityUtils.getSubject() 这个用户登录信息的存储是使用 InheritableThreadLocal来实现的
一下代码简单还原了上述过程
1 | public static void main(String[] args) throws Exception { |
可以看到执行用户B任务的时候获取到的却是用户A的登录信息。
这是因为前面说过了,使用 InheritableThreadLocal时 创建线程的时候将父线程的值传递给子线程
用户A 先发出请求,提交任务到线程池,这个时候会创建工作线程 worker,这个worker 是由threadA创建的
用户B提交任务的时候,使用的还是由threadA创建的工作线程。
这段看不懂的话,可以先去看看线程池相关的文章java多线程之ThreadPoolExecutor
解决方案
shiro提供了SecurityUtils.getSubject().associateWith(Callable) 方法,
这个方法 重新包装了一下 Callable 或者runable 方法 执行任务之前绑定用户信息,任务执行完成解绑
1 | public class SubjectCallable<V> implements Callable<V> { |
简单来说就是这样
1 | Thread threadA = new Thread(() -> { |
内存泄漏?
ThreadLocal实例被线程的ThreadLocalMap实例持有,也可以看成被线程持有。如果像使用了线程池,那么threadLocal就一直不会被垃圾回收,直到线程池中该线程被释放?
是这样吗?
看源码 ThreadLocalMap
1 | */ |
可见ThreadLocalMap持有的是ThreadLocal的 弱引用WeakReference,弱引用不会阻止垃圾回收,没有强引用指向该ThreadLocal对象时,你会发现使用get时突然返回null,
所以不会内存泄漏的,放心使用。