ThreadLocal是什么鬼?
前言
之前写过一篇关于[SimpleDateFormat线程安全问题](link: http://zhengyk.cn/2017/10/31/java/Thread_sdf/)的记录博客,里面提到了使用 ThreadLocal 来保证 SimpleDateFormat 线程安全,这是一种比在每个方法里都 new SimpleDateFormat更优雅的方式,当然现在操作时间推荐使用 joda-time ,joda-time线程安全且功能丰富、性能高。
其实网上介绍 ThreadLocal的文章也是很多很多,但我觉得自己还是得总结下来,一是记录自己的理解,二是方便以后查漏补缺。
那么ThreadLocal 是什么呢?
ThreadLocal
ThreadLocal官话解释是线程本地存储,它为变量在每个线程中都创建了一个副本,那么每个线程访问自己的变量副本,自然实现了变量的线程隔离。
代码示例:
1 | public class ThreadLocalTest { |
结合 ThreadLocal 和 Thread 的源码,草画了一张ThreadLocal 的结构图:
每个Thread内有一个 ThreadLocalMap,这个 ThreadLocalMap 的 每个 key 就是 每个ThreadLocal 对象(THREAD_LOCAL_A、THREAD_LOCAL_B),value 是变量副本。
1 | /** Thread类中的ThreadLocalMap */ |
ThreadLocalMap 是 ThreadLocal 里的静态内部类,ThreadLocalMap由 ThreadLocal 来操作。
这样,每个线程都有属于自己的变量副本,实现了变量线程隔离。
一些常用方法
void set(T value)
1 | public void set(T value) { |
T get()
1 | public T get() { |
ThreadLocalMap
前面提到 ThreadLocalMap 是 ThreadLocal 的静态内部类,key 是 ThreadLocal对象本身,value 是变量副本
1 | static class ThreadLocalMap { |
可以看到 ThreadLocalMap使用 Entry 来存储 K-V,而且这个 Entry 的 key 是弱引用,下次 GC 时要被回收,那就可能存在内存泄露问题。
ThreadLocal存在的内存泄露问题:由于ThreadLocalMap 存在于 Thread 中,和线程的生命周期一样,但是 ThreadLocal 在类里,往往 ThreadLocal 的生命周期比ThreadLocalMap要长(ThreadLocal 声明为 static 后生命周期会更长),由于Entry 使用ThreadLocal的弱引用,当 ThreadLocal 没有外部的强引用引用它时,下次 GC ThreadLocal 弱引用会被回收,而 value 是强引用,所以如果当前线程一直不结束,那么ThreadLocalMap 中就会存在key 为 null的数据,造成内存泄露。
避免内存泄露:用完后及时调用 remove 方法。
ThreadLocal 实践
众所周知,可以利用Spring 的 AOP 来记录日志,有环绕通知、前置通知、后置通知、异常通知等,加入我想实现一个记录某个方法执行的时间,使用环绕通知是可以解决的,那如果使用前置通知和后置通知呢?
前置通知和后置通知是不在同一个方法里的,所以不能像环绕通知那样在执行方法前后记录时间。
这时候可以利用 ThreadLocal 来实现。
代码示例:
1 | public class RecordTime { |
ThreadLocal 在框架中的应用
在 Spring 框架中,很多地方使用到了 ThreadLocal,如 **RequestContextHolder **请求上下文类,当我们在 service 层用到 request 或 controller 时,可以直接从 controller 层传递request、response 到 service 层,但是这样的话方法参数变多,代码不简洁。
其实 Spring 提供的RequestContextHolder类,可以让我们直接在service 层中获取 request 和 response
代码实例:
1 | RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); |
我们只要封装一个获取 request 和response 的方法抽取去 BaseService 中即可,这样使用更为优雅、简洁。
RequestContextHolder 就是利用 ThreadLocal 与当前请求的线程绑定来实现的:
1 | public abstract class RequestContextHolder { |
- Title: ThreadLocal是什么鬼?
- Author: 薛定谔的汪
- Created at : 2018-09-01 18:01:54
- Updated at : 2023-11-17 19:37:37
- Link: https://www.zhengyk.cn/2018/09/01/java/ThreadLocal/
- License: This work is licensed under CC BY-NC-SA 4.0.