ThreadLocal概念以及使用场景
ThreadLocal概念以及使用场景
根据自身的知识深度,这里只限于自己使用和学习的知识点整理,原理的解释还需要再沉淀。
该文章从项目开发中举例,希望能帮助到各位,不了解ThreadLocal的朋友,可能会问,这是个是什么,这有什么用,这能用在哪些地方,接下来我一一解释,如果有地方解释不好,希望多多包含。
概念:
这是个是什么:
ThreadLocal是一个类,是一个本地线程,提供了一种线程安全的方式,用来避免共享数据(线程变量隔离)。
翻看源码可以看到注释(已翻译):
此类提供线程局部变量。 这些变量不同于它们的普通对应变量,因为每个访问一个(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)
这有什么用:
按照《Java核心卷一》来说,有时候可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例;
就是说,每个线程都有一个伴生的空间(ThreadLocal),存储私有的数据。
只要线程存在,就能拿到对应的ThreadLocal中存储的值。
创建以及提供的方法
创建一个线程局部变量,其初始值通过调用给定的提供者(Supplier)生成;
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
使用的方法也比较少:
这里就列出用的比较多的方法:
返回此线程局部变量的当前线程副本中的值。 如果该变量对于当前线程没有值,则首先将其初始化为调用initialValue方法返回的值:
T get()
将此线程局部变量的当前线程副本设置为指定值;
value -- 要存储在此线程本地的当前线程副本中的值
void set(T value)
删除此线程局部变量的当前线程值(删除这里有些讲究,暂且不表,留在后面)
void remove()
项目实例:
ThreadLocal使用的场景主要是:(引用)
每个线程需要自己独立的实例且该实例需要在多个方法中使用
以下是个人使用的场景:
自己为什么会使用它,我是想在项目中直接获取当前用户的信息,这个功能就使用了ThreadLocal;
首先创建一个ThreadLocal类:
import com.xbhog.springbootvueblog.pojo.SysUser;
public class UserThreadLocal {
private UserThreadLocal(){}
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
public static SysUser get(){
return LOCAL.get();
}
public static void remove(){
LOCAL.remove();
}
}
其中包含了数据的添加删除和获取,因为我们需要的是用户信息,那么就把User类传入泛型中;
操作的对象有了,接下来就是使用:
在该项目中个人使用的地方在登录拦截器中,当对登录的信息检查成功后,那么将当前的用户对象加入到ThreadLocal中,
//登录验证成功,放行
System.out.println("走到UserThreadLocal--------");
UserThreadLocal.put(sysUser);
在controller中使用的时候,直接调用ThreadLocal中的get方法,就可以获得当前用户的信息:
//获取当前在线用户信息
SysUser sysUser = UserThreadLocal.get();
资源调用完成后需要在拦截器中删除ThreadLocal资源:
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//使用完的用户信息需要删除,防止内存泄露
UserThreadLocal.remove();
}
afterCompletion作用:
实现最终的完成后进行处理,该方法在执行完控制器之后执行,由于是在Controller方法执行完毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作
什么意思呢:
程序首先执行拦截器类中的preHandle()方法,如果该方法返回值是true,则程序会继续向下执行处理器中的方法,否则不再向下执行;在业务控制器类Controller(这里就可以用ThreadLocal 获取用户信息)处理完请求后,会执行postHandle()方法,
而后会通过DispatcherServlet向客户端返回响应;在DispatcherServlet处理完请求后,才会执行afterCompletion()方法(这里删除这次请求的ThreadLocal 中的user信息);
使用场景2
项目中针对日期进行的格式化SimpleDateFormat
,众所周知,其使用是线程不安全的,所以在项目中通过ThreadLocal来进行变量副本的创建;
为什么选择 ThreadLocal 呢? 其实也可以使用局部变量实现,但是有点low;通过ThreaLocal实现的线程变量隔离,是一个无锁的操作,对程序的性能有一定的提升。
为什么说是一个无锁的性能,因为SimpleDateFormat
是线程不安全的,平常的解决方式加锁,像synchronized实现,都需要进行同步,既然同步了,那么必然会有性能减少的问题。
ThreadLocal
和synchronized
的主要区别就是,threadLocal
是空间换时间,synchronized
是时间换空间.
ThreadLocal的内存泄露问题:
如果我们在使用完该线程后不进行ThreadLocal 中的变量进行删除,那么就会造成内存泄漏的问题,那么该问题是怎么出现的?
首先先看一下ThreadLocal 内部结构:
首先对于对象在栈中保存的是对象的引用,对象的存储在堆中,要先明确概念。栈中的格式也符合我们上述我们画的图1,其中Heap中的map是ThreadLocal map,里面包含key和value,其中value就是我们需要保存的变量数据,key则是ThreadLocal实例;
即:每一个Thread维护一个ThreadLocalMap, key为使用弱引用的ThreadLocal实例,value为线程变量的副本。
还需要注意的是上述图片的连接有实线和虚线,实线代表强引用,虚线表示弱引用;
扫盲强引用、软引用、弱引用、虚引用:😂
- 强引用,我们使用的大部分引用都是强引用,特点就是当内存不足时,垃圾回收器宁愿自己抛出OOM,也不会回收强引用来解决内存不足的问题
- 软引用,就是生杀大权都在垃圾回收器中,我内存够的话,你可以活着,如果不够的话,我就回收你,给我腾地方;
- 弱引用,只要垃圾回收器扫到,不管空间够不够,都给我回收了;
- 虚引用,形同虚设的东西,在任何情况下都可能被回收
这里只给出了概念,如果感兴趣可以自行了解作用环境。
那么知道强引用、弱引用后我们再来看图,因为ThreadLocal与线程属于同一个生命周期,当在某一个阶段进行了一次GC,那么当前线程还在,但是ThreadLocal实例被回收了,也就是key没了,
我们都知道,map中的value需要key找到,key没了,那么value就会永远的留在内存中,直到内存满了,导致OOM,所以我们就需要使用完以后进行手动删除,这样能保证不会出现因为GC的原因造成的OOM问题;
参考文献:
写的太细了!Spring MVC拦截器的应用,建议收藏再看!
结束:
如果你看到这里或者正好对你有所帮助,希望能点个👍或者⭐感谢;
有错误的地方,欢迎在评论指出,作者看到会进行修改。