最直观的ThreadLocal原理与用法详解

简介

ThreadLocal是Java标准库中提供的,用于在多线程环境中,为每个线程创建相互隔离的变量副本的类。通过线程隔离的方式防止共享资源所产生的线程安全问题。并且线程可以在代码执行的任意地点拿到存储在ThreadLocal中的变量。

原理

源码分析

观察ThreadLocal类源码我们可以发现,其中并没有关于存入的成员变量的定义,在对存入元素执行get和set操作时,读写的并非是ThreadLocal内部的成员变量的值,而是通过执行相应业务逻辑从Thread线程类中读写的。让我们观察ThreadLocal的Set方法。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

首先获取了当前线程t,然后以t为参数获取了线程的成员ThreadLocalMap,这个变量是一个以ThreadLocal<>为key,以待存入值为value的Map。通过当前threadLocal,即可访问到value。
ThreadLocalMap是Thread的成员变量,而ThreadLocal是用来操作的窗口。
因此,多线程情况下,每个线程使用同一个ThreadLocal变量进行共享资源操作而不会导致线程安全的原因,就在于ThreadLocal将共享资源放在了各个线程的内部。

流程图

用法

web用户身份信息存储

在web系统当中,处理一次请求往往需要跟踪用户的会话信息,例如在业务层中拿到当前登录用户的id等信息。但整个流程的执行是分散的(存在拦截器、过滤器、aop、鉴权框架、控制层),彼此之间难以共享信息。但由于这些流程都是由一个线程处理的,因此可以在首次获取用户信息时,将用户信息存储在ThreadLocal中,由于前面提到的原理,可以在另一处拿到属于该线程的,相同的用户信息。

public class LoginContext {
    private static final ThreadLocal<UserDO> loginUser = new ThreadLocal<>();

    public static void setLoginUser(UserDO user) {
        loginUser.set(user);
    }
    public static UserDO getLoginUser() {
        return loginUser.get();
    }
}


// token拦截器等
   user = token.getUser();
   LoginContext.setLoginUser(user);        // 设置登录上下文


// Controller
    @GetMapping("/info")
    public UserDO user() {
        UserDO user = LoginContext.getLoginUser();
        System.out.println("当前登录用户:"+user);
        return user;
    }
日志记录

以线程(web中的单次请求)为单位进行日志记录,同时不需要进行参数的传递,可以在任意代码执行地点拿到日志上下文并进行记录

简化参数传递

在调用层次较深,参数传递较为复杂的情况下,可以在不同业务层中通过ThreadLocal进行数据传输,以达到无感传递复杂参数的目的。

潜在问题

内存泄漏

通过ThreadLocal的原理,我们可以知道其所记录的变量是和线程高度绑定的。这在使用线程池的情况下,较容易产生内存泄漏的问题。一是新的任务可能会读取到旧任务在ThreaLocal中的残留值,导致数据安全问题。二是ThreadLocalMap中的value可能会泄漏,导致内存溢出的风险。

关于value的内存泄漏,我们都知道ThreadLocalMap的key是弱引用ThreadLocal的,也就意味着如果在线程执行过程中,ThreadLocal在外部解除强引用后,ThreadLocalMap的key由于弱引用,key实例就会随之被回收变为null。但是value还被强引用导致无法回收,因此产生了泄漏。

解决这一问题的方法,通常是在使用完threadLocal之后,调用其remove()方法,清除掉ThreadLocalMap中的entry。并且将threadLocal定义为static final,保证其强引用的存在,避免提前被回收掉。

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值