ThreadLocal概念以及使用场景

ThreadLocal概念以及使用场景

根据自身的知识深度,这里只限于自己使用和学习的知识点整理,原理的解释还需要再沉淀。

该文章从项目开发中举例,希望能帮助到各位,不了解ThreadLocal的朋友,可能会问,这是个是什么,这有什么用,这能用在哪些地方,接下来我一一解释,如果有地方解释不好,希望多多包含。

概念:

这是个是什么:

ThreadLocal是一个类,是一个本地线程,提供了一种线程安全的方式,用来避免共享数据(线程变量隔离)。

翻看源码可以看到注释(已翻译):

此类提供线程局部变量。 这些变量不同于它们的普通对应变量,因为每个访问一个(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)

这有什么用:

按照《Java核心卷一》来说,有时候可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例;

就是说,每个线程都有一个伴生的空间(ThreadLocal),存储私有的数据。

image-20211014212949769

只要线程存在,就能拿到对应的ThreadLocal中存储的值。

创建以及提供的方法

创建一个线程局部变量,其初始值通过调用给定的提供者(Supplier)生成;

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

使用的方法也比较少:

image-20211014213830530

这里就列出用的比较多的方法:

返回此线程局部变量的当前线程副本中的值。 如果该变量对于当前线程没有值,则首先将其初始化为调用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实现,都需要进行同步,既然同步了,那么必然会有性能减少的问题。

ThreadLocalsynchronized的主要区别就是,threadLocal是空间换时间,synchronized是时间换空间.

ThreadLocal的内存泄露问题:

如果我们在使用完该线程后不进行ThreadLocal 中的变量进行删除,那么就会造成内存泄漏的问题,那么该问题是怎么出现的?

首先先看一下ThreadLocal 内部结构:

首先对于对象在栈中保存的是对象的引用,对象的存储在堆中,要先明确概念。栈中的格式也符合我们上述我们画的图1,其中Heap中的map是ThreadLocal map,里面包含key和value,其中value就是我们需要保存的变量数据,key则是ThreadLocal实例;

即:每一个Thread维护一个ThreadLocalMap, key为使用弱引用的ThreadLocal实例,value为线程变量的副本。

还需要注意的是上述图片的连接有实线和虚线,实线代表强引用,虚线表示弱引用;

扫盲强引用、软引用、弱引用、虚引用:😂

  1. 强引用,我们使用的大部分引用都是强引用,特点就是当内存不足时,垃圾回收器宁愿自己抛出OOM,也不会回收强引用来解决内存不足的问题
  2. 软引用,就是生杀大权都在垃圾回收器中,我内存够的话,你可以活着,如果不够的话,我就回收你,给我腾地方;
  3. 弱引用,只要垃圾回收器扫到,不管空间够不够,都给我回收了;
  4. 虚引用,形同虚设的东西,在任何情况下都可能被回收

这里只给出了概念,如果感兴趣可以自行了解作用环境。

那么知道强引用、弱引用后我们再来看图,因为ThreadLocal与线程属于同一个生命周期,当在某一个阶段进行了一次GC,那么当前线程还在,但是ThreadLocal实例被回收了,也就是key没了,

我们都知道,map中的value需要key找到,key没了,那么value就会永远的留在内存中,直到内存满了,导致OOM,所以我们就需要使用完以后进行手动删除,这样能保证不会出现因为GC的原因造成的OOM问题;

image-20211015145718238

参考文献:

ThreadLocal解决了什么问题

SpringMVC---拦截器

写的太细了!Spring MVC拦截器的应用,建议收藏再看!

结束:

如果你看到这里或者正好对你有所帮助,希望能点个👍或者⭐感谢;

有错误的地方,欢迎在评论指出,作者看到会进行修改。

posted @ 2021-10-15 15:11  Xbhog  阅读(2918)  评论(0编辑  收藏  举报