ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal解析

文章目录使用场景ThreadLocalDemoThreadLocal存储结构关键APIset方法实现get方法实现缺点内存泄露父子线程传值问题InheritableThreadLocal线程上下文复制Demo缺点TransmittableThreadLocalDemo原理TransmittableThreadLocal#holderTransmittableThreadLocal#set捕获与重放...
摘要由CSDN通过智能技术生成

使用场景

在我们日常 Java Web 开发中难免遇到需要把一个参数层层的传递到最内层。

例如,用户进行操作需要在拦截器中从redis等缓存中间件去获取用户信息并判断是否过期,如果接下来的的业务方法需要用到用户信息时怎么获取呢?

Java的Web项目大部分都是基于Tomcat,每次访问都是一个新的线程,这样让我们联想到了ThreadLocal,每一个线程都独享一个ThreadLocal,在接收请求的时候set特定内容,在需要的时候get这个值。

先附上本文中demo源码演示地址,有兴趣的可以看下

ThreadLocalDemo地址

ThreadLocal

Demo

模拟一个普通的用户请求(新启动一个线程)

  1. 请求先经过拦截器,拦截器中必然需要获取用户信息,同时调用ThreadLocal.set(userInfo)将用户信息塞入线程上下文中
  2. 进行业务处理(业务处理时从ThreadLocal中获取用户信息,避免参数层层传递)

ThreadLocal封装类

public class ThreadLocalHolder {
   
    /**
     * 普通THREAD_LOCAL
     */
    private static final ThreadLocal<UserInfo> THREAD_LOCAL = new ThreadLocal<>();

    public static UserInfo getUser() {
   
        return THREAD_LOCAL.get();
    }

    public static void setUser(UserInfo userInfo) {
   
        THREAD_LOCAL.set(userInfo);
    }


}

测试类,启动一个线程模拟一个普通的Web同步请求

public static void main(String[] args) {
   
        BusinessService businessService = new BusinessService();
        LoginInterceptor loginInterceptor = new LoginInterceptor();
        //模拟一个普通的同步web请求
        new Thread(() -> {
   
            // 模拟用户身份拦截器
            loginInterceptor.userInterceptor();
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalHolder.getUser());
            // 拦截器通过后 同步处理业务
            businessService.doBusiness();
        }).start();
        
    }

模拟Web项目中的拦截器实现,从缓存中获取用户信息,塞入ThreadLocal中

public class LoginInterceptor {
   
    /**
     * 模拟拦截方法
     */
    public void userInterceptor() {
   
        UserInfo userInfo = getUserFromRedis();
        //将用户信息塞入ThreadLocal中
        ThreadLocalHolder.setUser(userInfo);
    }

    /**
     * 模拟从redis中获取信息,这里写死直接返回
     *
     * @return
     */
    public UserInfo getUserFromRedis() {
   
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1L);
        userInfo.setUserName("chenyin");
        return userInfo;
    }
}

业务处理类,获取用户信息,再处理业务

public class BusinessService {
   
    /**
     * 模拟同步处理业务
     */
    public void doBusiness() {
   
        //获取用户信息,避免显示参数传递
        System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalHolder.getUser());
        //业务处理。。略去
    }
    /**
     * 模拟异步处理业务
     */
    public void doBusinessAsync() {
   
        new Thread(() -> {
   
            //获取用户信息,避免显示参数传递
            System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalHolder.getUser());
            //业务处理。。略去
        }).start();

    }

}

main方法执行结果如下
在这里插入图片描述
可以看到,同一个线程中,即无论调用层级多深,也不需要将UserInfo作为参数层层传递,直接调用ThreadLocal.get()方法即可获取用户信息

ThreadLocal存储结构

首先提出一个问题,ThreadLocal中set()方法设的值具体存储在哪里?

先看几个关键的变量定义

Thread类中变量定义

public class Thread implements Runnable {
   
	//略去其他变量定义
	//普通线程上下文存储所在Map
    ThreadLocal.ThreadLocalMap threadLocals = null;
    //InheritableThreadLocal可继承线程本地变量存储所在Map
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

ThreadLocal.ThreadLocalMap中变量定义,Entry中的Key(ThreadLocal类型)是个WeakReference弱引用

static class ThreadLocalMap {
   
        static class Entry extends WeakReference<ThreadLocal<?>> {
   
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
   
                super(k);
                value = v;
            }
        }
        private Entry[] table;
}

再看下set方法实现

public void set(T value) {
   
		//获取当前线程对象t
        Thread t = Thread.currentThread();
        //获取t中的 ThreadLocal.ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        //往ThreadLocalMap中的tables中加入数据,key为当前ThreadLocal对象,value为用户传入的值
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal之所以能做不到不同线程之间的隔离性,就是因为set方法设的值不是存在我们定义的ThreadLocal变量中,而是存储在每个线程的变量(ThreadLocal.ThreadLocalMap)中

再提出一个问题,ThreadLocalMap.Entry中的key值为什么是ThreadLocal类型?

假设有如下场景,同一个线程中同时使用了2个ThreadLocal

public static void main(String[] args) {
   
        ThreadLocal<Integer> threadLocalA = new ThreadLocal<>();
        ThreadLocal<Integer> threadLocalB = new ThreadLocal<>();
        threadLocalA.set(1);
        threadLocalB.set(2);
    }

同一线程中可能定义了不同的ThreadLocal变量,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的,因此Key为ThreadLocal能够根据ThreadLocal中的hashCode唯一确定其value在table中的下标

关键API

//从线程上下文中获取值
public T get() ;
//将值设入线程上下文中,供同一线程后续使用
public void set(T value) ;
//清除线程上下文
public void remove() ;
set方法实现

源码如下

public void set(T value) {
   
		//获取当前
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值