Java线程之间数据传递的实现示例(4种)

 更新时间:2023年08月06日 09:00:48   作者:立莹Sir  
我们经常会遇到父子线程数据传递(非调用参数)的场景,本文主要介绍了Java线程之间数据传递的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

在业务系统的开发过程中,我们经常会遇到父子线程数据传递(非调用参数)的场景,如:登陆信息,调用者信息,TraceId的传递等业务场景,固总结4中方式进行线程之间数据传递。

ThreadLocal

代码如下:

public class TtlParameterWrapper {
    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
    private TtlParameterWrapper() {
    }
    public static String getCaller() {
        return THREAD_LOCAL.get();
    }
    public static void setCaller(String caller) {
       THREAD_LOCAL.set(caller);
    }
    public static void clear() {
       THREAD_LOCAL.remove();
    }
}

那么子线程想要获取这个TtlParameterWrapper如何做呢?

  • 获取父线程的TtlParameterWrapper
  • 将TtlParameterWrapper设置到子线程,达到复用
public void handler(){
        // 1. 获取父线程
       TtlParameterWrapper.setCaller("caller path");
       log.info("父线程的值 ->{}",TtlParameterWrapper.get());
       CompletableFuture.runAsync(()->{
            // 2. 设置子线程的值,复用
          TtlParameterWrapper.setCaller("caller path");
          log.info("子线程的值 ->{}", TtlParameterWrapper.getCaller());
        });
    }

总结

虽然最终达成了传递的目的,但是每次开异步线程都需要手动设置,代码冗余繁杂,如果不这样设置则无法跨线程进行传递;如果手动设置,将无法进行线程间进行传递,因为TheadLocal中的数据无法进行线程间进行传递。

InheritableThreadLocal

这种方案不建议使用,InheritableThreadLocal虽然能够实现父子线程间的复用,但是在线程池中使用会存在失败的问题,原因:InheritableThreadLocal 在父线程创建子线程的时候,会将父线程中InheritableThreadLocal中存储的数据 拷贝一份存储到子线程的 InheritableThreadLocal中,但是在web的容器中使用了线程池,线程会被创建回收重复的利用,不会被销毁重新创建,所以会存在实效的场景。

这种方案使用也是非常简单,直接用InheritableThreadLocal替换ThreadLocal即可。

代码如下:

public class TtlParameterWrapper {
    private static  final  InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
    public static String getCaller(){
        return inheritableThreadLocal.get();
    }
    public static void setCaller(LoginVal loginVal){
       inheritableThreadLocal.set(loginVal);
    }
    public static void clear(){
       inheritableThreadLocal.remove();
    }
}

TransmittableThreadLocal

TransmittableThreadLocal是阿里开源的工具,解决了InheritableThreadLocal不能进行线程池间传递数据的缺陷,在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。

添加依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.14.2</version>
</dependency>

TtlParameterWrapper改造

public class TtlParameterWrapper {
    private static final TransmittableThreadLocal<String> TRANSMITTABLE_THREAD_LOCAL = new TransmittableThreadLocal<>();
    private TtlParameterWrapper() {
    }
    public static String getCaller() {
        return TRANSMITTABLE_THREAD_LOCAL.get();
    }
    public static void setCaller(String caller) {
       TRANSMITTABLE_THREAD_LOCAL.set(caller);
    }
    public static void clear() {
        TRANSMITTABLE_THREAD_LOCAL.remove();
    }
}

原理

从定义来看,TransimittableThreadLocal继承于InheritableThreadLocal,并实现TtlCopier接口,它里面只有一个copy方法。所以主要是对InheritableThreadLocal的扩展。

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T>

在TransimittableThreadLocal中添加holder属性。这个属性的作用就是被标记为具备线程传递资格的对象都会被添加到这个对象中。
要标记一个类,比较容易想到的方式,就是给这个类新增一个Type字段,还有一个方法就是将具备这种类型的的对象都添加到一个静态全局集合中。之后使用时,这个集合里的所有值都具备这个标记。

//1. holder本身是一个InheritableThreadLocal对象
//2. 这个holder对象的value是WeakHashMap<TransmittableThreadLocal<Object>,?>
//   2.1WeekHashMap的value总是null,且不可能被使用。
//    2.2WeekHasshMap支持value=null
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>>(){
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}
/**
 * 重写了childValue方法,实现上直接将父线程的属性作为子线程的本地变量对象。   
 */
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?>parentValue){
    return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}};

应用代码是通过TtlExecutors工具类对线程池对象进行包装。工具类只是简单的判断,输入的线程池是否已经被包装过、非空校验等,然后返回包装类ExecutorServiceTtlWrapper。根据不同的线程池类型,有不同和的包装类。

@Nullable
public static ExecutorServicegetTtlExecutorService(@Nullable ExecutorService executorService){
    if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced){
        return executorService;
    }
    return new ExecutorServiceTtlWrapper(executorService);
}

进入包装类ExecutorServiceTtlWrapper。可以注意到不论是通过ExecutorServiceTtlWrapper#submit方法或者是ExecutorTtlWrapper#execute方法,都会将线程对象包装成TtlCallable或者TtlRunnable,用于在真正执行run方法前做一些业务逻辑。

/** 
 * 在ExecutorServiceTtlWrapper实现submit方法
 */
@NonNull
@Override
public <T> Future<T> submit(@NonNull Callable<T> task){
    return executorService.submit(TtlCallable.get(task));
}
/** 
 * 在ExecutorTtlWrapper实现execute方法
 */
@Override
public void execute(@NonNull Runnable command){
    executor.execute(TtlRunnable.get(command));
}

重点的核心逻辑应该是在TtlCallable#call()或者TtlRunnable#run()中。以下以TtlCallable为例,TtlRunnable同理类似。在分析call()方法之前,先看一个类Transmitter。

public static class Transmitter {
    /**    
     * 捕获当前线程中的是所有TransimittableThreadLocal和注册ThreadLocal的值。    
     */
    @NonNull
    public static Object capture(){
        return new Snapshot(captureTtlValues(), captureThreadLocalValues());
    }
    /**    
     * 捕获TransimittableThreadLocal的值,将holder中的所有值都添加到HashMap后返回。    
     */
    private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
        HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
        for(TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
            ttl2Value.put(threadLocal, threadLocal.copyValue());
        }
        return ttl2Value;
    }
    /**    
     * 捕获注册的ThreadLocal的值,也就是原本线程中的ThreadLocal,可以注册到TTL中,在    
     * 进行线程池本地变量传递时也会被传递。   
     */
    private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
        for(Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            final TtlCopier<Object> copier = entry.getValue();
            threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
        }
        return threadLocal2Value;
    }
    /**    
     * 将捕获到的本地变量进行替换子线程的本地变量,并且返回子线程现有的本地变量副本backup。   
     * 用于在执行run/call方法之后,将本地变量副本恢复。    
     */
    @NonNull
    public static Object replay(@NonNull Object captured) {
        final Snapshot capturedSnapshot = (Snapshot) captured;
        return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
    }
    /**    
     * 替换TransmittableThreadLocal    
     */
    @NonNull
    private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> captured) {
        // 创建副本backup
        HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();
        for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();) {
            TransmittableThreadLocal<Object> threadLocal = iterator.next();
            // 对当前线程的本地变量进行副本拷贝
            backup.put(threadLocal, threadLocal.get());
            // 若出现调用线程中不存在某个线程变量,而线程池中线程有,则删除线程池中对应的本地变量
            if (!captured.containsKey(threadLocal)){
                iterator.remove();
                threadLocal.superRemove();
            }
        }
        // 将捕获的TTL值打入线程池获取到的线程TTL中。
        setTtlValuesTo(captured);
        // 是一个扩展点,调用TTL的beforeExecute方法。默认实现为空
        doExecuteCallback(true);
        return backup;
    }
    private static HashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> captured) {
        final HashMap<ThreadLocal<Object>, Object> backup = new HashMap<ThreadLocal<Object>, Object>();
        for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            backup.put(threadLocal, threadLocal.get());
            final Objectvalue = entry.getValue();
            if (value == threadLocalClearMark) threadLocal.remove();
            else threadLocal.set(value);
        }
        return backup;
    }
    /**    
     * 清除单线线程的所有TTL和TL,并返回清除之气的backup    
     */
    @NonNull
    public static Object clear() {
        final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
        for (Map.Entry<ThreadLocal<Object>,TtlCopier<Object>>entry : threadLocalHolder.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal2Value.put(threadLocal, threadLocalClearMark);
        }
        return replay(new Snapshot(ttl2Value, threadLocal2Value));
    }
    /**    
     * 还原    
     */
    public static void restore(@NonNull Object backup) {
        final Snapshot backupSnapshot = (Snapshot) backup;
        restoreTtlValues(backupSnapshot.ttl2Value);
        restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
    }
    private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> backup) {
        // 扩展点,调用TTL的afterExecute
        doExecuteCallback(false);
        for (finalIterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();) {
            TransmittableThreadLocal<Object> threadLocal = iterator.next();
            if (!backup.containsKey(threadLocal)) {
                iterator.remove();
                threadLocal.superRemove();
            }
        }
        // 将本地变量恢复成备份版本
        setTtlValuesTo(backup);
    }
    private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> ttlValues) {
        for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry :ttlValues.entrySet()) {
            TransmittableThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal.set(entry.getValue());
        }
    }
    private staticvoid restoreThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>,Object> backup) {
        for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
            final ThreadLocal<Object> threadLocal = entry.getKey();
            threadLocal.set(entry.getValue());
        }
    }
    /**   
     * 快照类,保存TTL和TL   
     */
    private static class Snapshot {
        final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
        final HashMap<ThreadLocal<Object>, Object> threadLocal2Value;
        private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object>ttl2Value, HashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
            this.ttl2Value= ttl2Value;
            this.threadLocal2Value= threadLocal2Value;
        }
    }
    // 进入TtlCallable#call()方法。
    @Override
    public V call() throws Exception {
        Object captured = capturedRef.get();
        if (captured == null|| releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) {
            throw new IllegalStateException("TTLvalue reference is released after call!");
        }
        // 调用replay方法将捕获到的当前线程的本地变量,传递给线程池线程的本地变量,
        // 并且获取到线程池线程覆盖之前的本地变量副本。
        Object backup = replay(captured);
        try {
            // 线程方法调用
            return callable.call();
        } finally {
            // 使用副本进行恢复。
            restore(backup);
        }
    }
}

线程池方式传递本地变量的核心代码已经完毕。总的来说在创建TtlCallable对象是,调用capture()方法捕获调用方的本地线程变量,在call()执行时,将捕获到的线程变量,替换到线程池所对应获取到的线程的本地变量中,并且在执行完成之后,将其本地变量恢复到调用之前。

TaskDecorator

线程池设置TaskDecorator,TaskDecorator是什么?

官方释义:这是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息。

代码如下

public class ContextTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable){
        //获取父线程的值
        String callerPath = TtlParameterWrapper.getCaller();
        return () -> {
            try {
                // 将主线程的请求信息,设置到子线程中
               TtlParameterWrapper.setCaller(callerPath);
                // 执行子线程,这一步不要忘了
               runnable.run();
            } finally {
                // 线程结束,清空这些信息,否则可能造成内存泄漏
               TtlParameterWrapper.clear();
            }
        };
    }
}

TaskDecorator需要结合线程池使用,实际开发中异步线程建议使用线程池,只需要在对应的线程池配置一下

代码

@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
       ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
       poolTaskExecutor.setCorePoolSize(xx);
       poolTaskExecutor.setMaxPoolSize(xx);
        // 设置线程活跃时间(秒)
       poolTaskExecutor.setKeepAliveSeconds(xx);
        // 设置队列容量
       poolTaskExecutor.setQueueCapacity(xx);
        //设置TaskDecorator,用于解决父子线程间的数据复用
       poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
       poolTaskExecutor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
       poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
       return poolTaskExecutor;
}

此时业务代码就不需要去设置子线程的值,直接使用即可

代码

public void handlerAsync() {
       log.info("父线程的用户信息 -> {}", TtlParameterWrapper.get());
        //执行异步任务,需要指定的线程池
       CompletableFuture.runAsync(() -> 
           log.info("子线程的用户信息 -> {}", TtlParameterWrapper.get()
       ),taskExecutor);
}

这里使用的是CompletableFuture执行异步任务,使用@Async这个注解同样是可行的。

注意:无论使用何种方式,都需要指定线程池

到此这篇关于Java线程之间数据传递的实现示例的文章就介绍到这了,更多相关Java线程之间数据传递内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java中orElse和orElseGet方法区别小结

    java中orElse和orElseGet方法区别小结

    这篇文章主要给大家介绍了关于java中orElse和orElseGet方法区别的相关资料,两者之间的区别细微,但是却在某些场景下显的很重要,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • Java深入分析讲解反射机制

    Java深入分析讲解反射机制

    反射是框架的灵魂,Java框架底层都是用反射机制+xml配置等来实现的,本文将通过示例详细讲解Java中的反射机制,感兴趣的小伙伴可以跟随小编学习一下
    2022-06-06
  • 解决JSTL foEach标签 刷新报错的方法

    解决JSTL foEach标签 刷新报错的方法

    本篇文章是对JSTL foEach标签刷新报错的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • Spring-IOC容器中的常用注解与使用方法详解

    Spring-IOC容器中的常用注解与使用方法详解

    Spring是一个轻量级Java开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题,这篇文章给大家详细介绍Spring-IOC容器中的常用注解与使用方法,感兴趣的朋友跟随小编一起看看吧
    2021-04-04
  • 利用Sharding-Jdbc进行分库分表的操作代码

    利用Sharding-Jdbc进行分库分表的操作代码

    sharding-jdbc是一个分布式的关系型数据库中间件,今天通过本文给大家介绍利用Sharding-Jdbc进行分库分表的操作代码,代码简单易懂对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-01-01
  • 详解Java中格式化日期的DateFormat与SimpleDateFormat类

    详解Java中格式化日期的DateFormat与SimpleDateFormat类

    DateFormat其本身是一个抽象类,SimpleDateFormat 类是DateFormat类的子类,一般情况下来讲DateFormat类很少会直接使用,而都使用SimpleDateFormat类完成,下面我们具体来看一下两个类的用法:
    2016-05-05
  • Java IO流和文件操作实现过程解析

    Java IO流和文件操作实现过程解析

    这篇文章主要介绍了Java IO流和文件操作实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Java循环调用多个timer实现定时任务

    Java循环调用多个timer实现定时任务

    这篇文章主要介绍了Java循环调用多个timer实现定时任务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • 深入理解java中的拷贝机制

    深入理解java中的拷贝机制

    这篇文章主要给大家深入介绍了java中的拷贝机制,网上关于java中拷贝的文章也很多,但觉得有必要再深的介绍下java的拷贝机制,有需要的朋友可以参考学习,下面来一起看看吧。
    2017-02-02
  • Spring Cloud Gateway整合sentinel 实现流控熔断的问题

    Spring Cloud Gateway整合sentinel 实现流控熔断的问题

    本文给大家介绍下 spring cloud gateway 如何整合 sentinel实现流控熔断,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友一起看看吧
    2022-02-02

最新评论