TransmittableThreadLocal跨线程共享变量

前言

上一篇讲了InheritableThreadLocal在父子线程之间支持上下文环境之间的访问,但是在线程池的场景下实现传递变量值还是不支持的,这种就要用到阿里的开源工具TransmittableThreadLocal,可以在线程池之间传递变量的值,在多线程并发对的场景下,我们一般都会使用线程池来减少创建线程带来的开销问题,需要实现变量之间的传递,就可以使用TransmittableThreadLocal进行存储;

TransmittableThreadLocal使用场景

例如在多线程环境进行登陆,需要获取当前线程变量中的,用户id、用户名称、Token信息等。

typescript 复制代码
//创建TransmittableThreadLocal存储map集合
    private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();


//获取TransmittableThreadLocal里面map值,然后进行赋值
    public static Map<String, Object> getLocalMap()
    {
        Map<String, Object> map = THREAD_LOCAL.get();
        if (map == null)
        {
            map = new ConcurrentHashMap<String, Object>();
            THREAD_LOCAL.set(map);
        }
        return map;
    }

// 获取TransmittableThreadLocal里面变量的值
public static String get(String key)
{
    Map<String, Object> map = getLocalMap();
    return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));
}

业务中有很多种情况都是涉及到这种场景,通过获取到用户的信息,来获取当前用户下面的一些权限;

csharp 复制代码
/**
 * 获取登录用户信息
 */
public static LoginUser getLoginUser()
{
    return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class);
}

TransmittableThreadLocal源码分析

那么TransmittableThreadLocal是如何在多线程之前实现变量共享的呢,那肯定离不开TtlRunnable简称TTL,先看TransmittableThreadLocal有一个静态的内部类Transmitter,它有这么几个关键的方法,主要就是capture(copy上下文)、replay(存放上下文)、clear(清理上下文)、restore(重置上下文);

less 复制代码
        @NonNull
        public static Object capture() {...}
        @NonNull
        public static Object replay(@NonNull Object captured){... }
        @NonNull
        public static Object clear() {.....}

        public static void restore(@NonNull Object backup) {...}

当然在每个方法里面都会定义一个map包装的Transmittee,Transmittee是个接口分别定义了capture、replay、clear、restore方法;

less 复制代码
final HashMap<Transmittee<Object, Object>, Object> transmittee2Value = newHashMap(transmitteeSet.size());

transmitteeSet的定义我们可以看到是用到了COW(CopyOnWrite)线程安全的并发容器;

typescript 复制代码
private static final Set<Transmittee<Object, Object>> transmitteeSet = new CopyOnWriteArraySet<>();
``

这里面HashMap的key定义可以看到是使用TransmittableThreadLocal,在进行put的操作的时候也用到了和InheritableThreadLocal同样的copyValue方法

在进行代码的追踪可以看到,使用的是SuppliedTransmittableThreadLocal类,定义的TtlCopier接口,调用它的copy方法进行多线程之间的变量copy;

该copy方法也是使用/TtlRunnable/ ,开启一个run方法,最终交给TtlRunnable进行处理;

TtlRunnable的run方法里面有个/replay/方法,返回当前线程的快照信息就是TransmittableThreadLocal里面存储的其他线程的变量信息,finally每次要调用/restore/方法重置当前快照信息,这里又回到上面所说的Transmitter的几个比较关键的方法,最终返回的Snapshot对象就是当前线程ThreadLocal引用;

整体一个流程就是,当前线程TtlRunnable开启一个run方法,先去线程池里去获取上下文信息,然后通过Transmitter实际的执行类,进行copy、replay、restore,存储到transmitteeSet里,通过访问TransmittableThreadLocal.hodler,获取所有的ThreadLocal持有的引用,返回Snapshot引用对象,在传递给TtlRunnable中,完成了一次copy操作;

执行结果验证

通过下面代码, 定义一个全局变量/transmittableThreadLocal/,方法内set进入一个value变量,定义线程池开启两个线程,来获取transmittableThreadLocal的变量,可以看到获取都是同一个transmittableThreadLocal实例变量;

总结

在并发量比较高的场景下,使用线程池的场景下,如果需要实现这种全局的线程变量之间的共享,不妨可以使用下TransmittableThreadLocal;

相关推荐
葫芦和十三22 分钟前
图解 MongoDB 22|读写关注:持久性与一致性的档位选择
后端·mongodb·agent
葫芦和十三7 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp7 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑8 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯9 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan11 小时前
多Agent之间的区别
后端
青石路13 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充13 小时前
1.面向对象设计思想
后端
IT_陈寒13 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro14 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端