如何解决异步任务上下文丢失问题

 更新时间:2024年09月18日 10:48:18   作者:勿语&  
在多线程编程中,异步任务可能会导致上下文信息丢失,为了解决这个问题,可以在执行异步任务前,通过自定义TaskDecorator拷贝主线程的上下文至子线程,这样可以确保上下文在异步执行过程中得以保留,将定制的TaskDecorator设置至线程池,可以有效地解决上下文丢失问题

解决异步任务上下文丢失问题

  • 上下文丢失主要是因为主线程和子线程的上下文不能共享。
  • 可以通过执行异步任务之前,将主线程上的上下文信息拷贝到子线程上。

自定义TaskDecorator 来拷贝主线程上的上下文信息到子线程,然后将自定义的 TaskDecorator实现类 设置到线程池上。

@Configuration
public class ThreadPoolConfig {

    @Bean(name = "customizeTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数=cpu核心数+1
        poolExecutor.setCorePoolSize(5);
        // 最大线程数=cpu核心数*2
        poolExecutor.setMaxPoolSize(8);
        // 设置任务装饰器
        poolExecutor.setTaskDecorator(taskDecorator());
        // 任务被拒绝后,交给调用线程执行
        poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return poolExecutor;
    }

    @Bean
    public TaskDecorator taskDecorator(){
        return new TaskDecorator() {
            @Override
            public Runnable decorate(Runnable runnable) {
                RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                return ()->{
                    try {
                        RequestContextHolder.setRequestAttributes(requestAttributes);
                        runnable.run();
                    }finally {
                        RequestContextHolder.resetRequestAttributes();
                    }
                };
            }
        };
    }
}

Fegin异步情况丢失上下文问题

在微服务的开发中,我们经常需要服务之间的调用,并且为了提高效率使用异步的方式进行服务之间的调用,在这种异步的调用情况下会有一个严重的问题,丢失上文下

通过以上图片可以看出异步丢失上下文的原因是不在同一个线程,所有数据不能共享,Wie了解决这个问题,我们就需要把之前线程的请求头上下文,在次存放到其他线程的请求头上下文就行,具体实现如下:

案例:feign异步获取订单明细的案例代码

/**
     * 获取订单明细的vo
     * @return
     */
    @Override
    public OrderConfirmVo orderConfirm() {
        MemberResponseVo member = OrderInterceptor.threadLocal.get();
 
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
        System.out.println("主线程:"+ Thread.currentThread().getId());
        //获取主线程的请求头信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
 
        //考虑到效率问题  使用异步编排
        CompletableFuture<Void> getAddress = CompletableFuture.runAsync(() -> {
            //子线程中设置添加主线程的请求头信息  信息共享  否则远程调用异步处理丢失请求头信息
            RequestContextHolder.setRequestAttributes(requestAttributes);
            System.out.println("address:"+ Thread.currentThread().getId());
            //远程获取地址信息
            List<MemberAddressVo> address = memberFeignService.getAddress(member.getId());
            orderConfirmVo.setAddress(address);
        }, executor);
 
 
        CompletableFuture<Void> getItem = CompletableFuture.runAsync(() -> {
            //子线程中设置添加主线程的请求头信息  信息共享  否则远程调用异步处理丢失请求头信息
            RequestContextHolder.setRequestAttributes(requestAttributes);
            System.out.println("item:"+ Thread.currentThread().getId());
            //远程获取购物项
            List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
            orderConfirmVo.setItems(currentUserCartItems);
        }, executor).thenRunAsync(()->{
            List<OrderItemVo> items = orderConfirmVo.getItems();
            //获取所有商品的id
            List<String> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            List<Long> skuIds = collect.stream().map(item -> {
                return Long.parseLong(item);
            }).collect(Collectors.toList());
            R<List<SkuHasStockVo>> skusHasStock = wmsFeignService.getSkusHasStock(skuIds);
            List<SkuHasStockVo> data = skusHasStock.getData(new TypeReference<List<SkuHasStockVo>>() {
            });
            if(data!= null){
                Map<Long, Boolean> collect1 = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
                orderConfirmVo.setStocks(collect1);
            }
        },executor);
 
        //异步编排完成之后执行后续操作
        try {
            CompletableFuture.allOf(getAddress,getItem).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
 
 
        orderConfirmVo.setIntegration(member.getIntegration());
 
        orderConfirmVo.setPayPrice(orderConfirmVo.getPayPrice());
        orderConfirmVo.setTotal(orderConfirmVo.getTotal());
        //TODO 放重处理  生成token令牌储存在redis
        String token = UUID.randomUUID().toString().replace("-", "");
        orderConfirmVo.setOrderToken(token);
        redisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN+member.getId(),token);
 
        return orderConfirmVo;
    }

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java检测死锁案例

    Java检测死锁案例

    这篇文章主要介绍了Java检测死锁案例,本文列举了导致死锁的程序,通过使用jconsole工具进行检测等,讲述了避免死锁的方法,需要的朋友可以参考下
    2021-07-07
  • IDEA巧用Postfix Completion让码速起飞(小技巧)

    IDEA巧用Postfix Completion让码速起飞(小技巧)

    这篇文章主要介绍了IDEA巧用Postfix Completion让码速起飞,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java将文件夹保留目录打包为 ZIP 压缩包并下载的教程详解

    Java将文件夹保留目录打包为 ZIP 压缩包并下载的教程详解

    这篇文章主要介绍了Java将文件夹保留目录打包为 ZIP 压缩包并下载的教程详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Spring boot使用多线程过程步骤解析

    Spring boot使用多线程过程步骤解析

    这篇文章主要介绍了Spring boot使用多线程过程步骤解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • 使用Java实现压缩图片,视频和音频

    使用Java实现压缩图片,视频和音频

    在 Java 中,要实现视频压缩通常需要使用外部的库或工具,下面就跟随小编一起来看看如何利用这些库实现压缩图片,视频和音频功能吧
    2024-03-03
  • Java微信小程序医院挂号系统

    Java微信小程序医院挂号系统

    这篇文章主要介绍了基于Java微信小程序医院挂号系统,可以实现远程处理事务,远程提交工作和随时追踪工作的状态,文中提供了解决思路和部分实现代码,需要的朋友可以参考下
    2023-03-03
  • Java中通过三级缓存解决Spring循环依赖详解

    Java中通过三级缓存解决Spring循环依赖详解

    这篇文章主要介绍了Java中通过三级缓存解决Spring循环依赖详解,当出现两个或多个 Bean 在初始化时相互依赖的情况时,Spring Boot 会将其中一个 Bean 提前暴露出来,以便其他 Bean 能够在初始化时正确地引用它,这一策略能有效避免循环依赖导致的问题,需要的朋友可以参考下
    2023-09-09
  • Spring boot 配置多个redis的方法示例

    Spring boot 配置多个redis的方法示例

    这篇文章主要介绍了Spring boot 配置多个redis的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • 使用jdbcTemplate查询返回自定义对象集合代码示例

    使用jdbcTemplate查询返回自定义对象集合代码示例

    这篇文章主要介绍了使用jdbcTemplate查询返回自定义对象集合代码示例,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • 详解java安全编码指南之可见性和原子性

    详解java安全编码指南之可见性和原子性

    java类中会定义很多变量,有类变量也有实例变量,这些变量在访问的过程中,会遇到一些可见性和原子性的问题。这里我们来详细了解一下怎么避免这些问题。
    2021-06-06

最新评论