@Async详解(二)

Spring异步注解@Async

@Async为什么要使用自定义线程池

通过上一篇对@Async的初步分析,我们可以知道当使用@Async不指定线程池时,Spring会默认使用SimpleAsyncTaskExecutor线程池,那么SimpleAsyncTaskExecutor有什么缺点呢?下面我们通过源码分析SimpleAsyncTaskExecutor为什么不建议使用。

SimpleAsyncTaskExecutor

此类特点概括为以下几点:

  • 为每个任务启动一个新线程,异步执行它。

  • 支持通过“concurrencyLimit”bean 属性限制并发线程。默认情况下,并发线程数是无限的。

  • 注意:此实现不重用线程!

  • 考虑一个线程池 TaskExecutor 实现,特别是用于执行大量短期任务。

默认情况下,SimpleAsyncTaskExecutor不会限制线程创建的个数,这会导致资源耗尽。这个线程池和我们印象中的的线程池可以说是相悖的。如果需要使用SimpleAsyncTaskExecutor,则需指定线程上限(调用setConcurrencyLimit方法),避免在极端情况下出现资源耗尽的问题。另外,该任务执行器并没有执行拒绝策略,这也是在线上环境需谨慎使用的原因之一。

@Async使用自定义线程池

自定义线程池配置:



@Slf4j
@Configuration
@EnableConfigurationProperties({TaskExecutorProperties.class,ExportExecutorProperties.class})
public class ExecutePoolConfig {

    // 普通任务线程池配置
    private final TaskExecutorProperties taskExecutorProperties;

    // 导出任务线程池配置
    private final ExportExecutorProperties exportExecutorProperties;

    @Autowired
    public ExecutePoolConfig(TaskExecutorProperties taskExecutorProperties,ExportExecutorProperties exportExecutorProperties) {
        this.taskExecutorProperties = taskExecutorProperties;
        this.exportExecutorProperties = exportExecutorProperties;
    }

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        return buildExecutor(taskExecutorProperties);
    }
    @Bean("exportExecutor")
    public Executor exportExecutor() {
        return buildExecutor(exportExecutorProperties);
    }

    /**
     * 构建线程池
     *
     * @param executorProperties ExecutorBaseProperties
     * @return {@link Executor}
     */
    private Executor buildExecutor(ExecutorBaseProperties executorProperties) {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        // 设置核心线程数
        executor.setCorePoolSize(executorProperties.getCorePoolSize());

        // 设置最大线程数
        executor.setMaxPoolSize(executorProperties.getMaxPoolSize());

        // 设置队列大小
        executor.setQueueCapacity(executorProperties.getQueueCapacity());

        // 设置线程池维护线程所允许的空闲时间
        executor.setKeepAliveSeconds(executorProperties.getKeepAliveSeconds());

        // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
        executor.setWaitForTasksToCompleteOnShutdown(true);

        // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁
        executor.setAwaitTerminationSeconds(30);

        // 设置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(executorProperties.getThreadNamePrefix());

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        RejectedExecutionHandler rejectedExecutionHandler = null;

        try {

            Class<?> clazz = Class
                .forName("java.util.concurrent.ThreadPoolExecutor$" + executorProperties.getRejectionPolicyName());
            rejectedExecutionHandler = (RejectedExecutionHandler)clazz.newInstance();

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            log.error("获取rejection-policy异常,请查看配置文件", e);
            return null;

        }

        executor.setRejectedExecutionHandler(rejectedExecutionHandler);
        // 执行初始化
        executor.initialize();
        return executor;

    }
}

@Data
@Component
@ConfigurationProperties(prefix = "task.executor.conf")
public class TaskExecutorProperties extends ExecutorBaseProperties {

    /**
     * 核心线程数
     */
    private int corePoolSize = 10;

    /**
     * 最大线程数
     */
    private int maxPoolSize = 20;

    /**
     * 队列大小
     */
    private int queueCapacity = 200;

    /**
     * 线程池维护线程所允许的空闲时间
     */
    private int keepAliveSeconds = 60;

    /**
     * 线程的名称前缀
     */
    private String threadNamePrefix = "taskExecutor-";

    /**
     * 拒绝策略
     */
    private String rejectionPolicyName = "CallerRunsPolicy";

}
@Data
public class ExecutorBaseProperties {

    /**
     * 核心线程数
     */
    private int corePoolSize = 10;

    /**
     * 最大线程数
     */
    private int maxPoolSize = 20;

    /**
     * 队列大小
     */
    private int queueCapacity = 200;

    /**
     * 线程池维护线程所允许的空闲时间
     */
    private int keepAliveSeconds = 60;

    /**
     * 线程的名称前缀
     */
    private String threadNamePrefix = "taskExecutor-";

    /**
     * 拒绝策略
     */
    private String rejectionPolicyName = "CallerRunsPolicy";

}

线程池的使用:


@RestController
public class TestController {

    @Resource
    private AsyncTestService asyncTestService;

    @RequestMapping("/exportTest")
    public String exportTest() {
        System.out.println("父线程userId:" + UserUtils.getUserId());
        System.out.println("父线程traceId:" + MDC.get("traceId"));
        for (int i = 0; i < 10; i++) {
            asyncTestService.exportTest();
        }
        return "OK";
    }

    @RequestMapping("/taskTest")
    public String taskTest() {
        for (int i = 0; i < 10; i++) {
            asyncTestService.taskTest();
        }
        return "OK";
    }

}

public interface AsyncTestService {
    /**
     * exportTest
     */
    void exportTest();

    /**
     * taskTest
     */
    void taskTest();
}

@Service
public class AsyncTestServiceImpl implements AsyncTestService {

    @Async("exportExecutor")
    @Override
    public void exportTest() {
        System.out.println("子线程userId:" + UserUtils.getUserId());
        System.out.println("子线程traceId:"  + MDC.get("traceId"));
        System.out.println("我是自定义类型的线程池:" + Thread.currentThread().getName());
    }

    @Async
    @Override
    public void taskTest() {
        System.out.println("我是beanName使用taskExecutor名字的线程:" + Thread.currentThread().getName());
    }
}

/**
 *使用ThreadLocal存储共享的数据变量,如登录的用户信息
 */
public class UserUtils {
    private static final ThreadLocal<String> userLocal = new ThreadLocal<>();

    public static String getUserId() {
        return userLocal.get();
    }

    public static void setUserId(String userId) {
        userLocal.set(userId);
    }

    public static void clear() {
        userLocal.remove();
    }

}

执行结果:

可以看到两种线程池都生效了,通过自定义配置可以让我们达到对@Async使用的掌控,以避免使用默认线程池带来的弊端。

但是也来了新的问题,在实际开发过程中我们需要父子线程之间传递一些数据,比如用户信息,分布式系统中用来链路追踪的tranceId等。通过上方的执行结果我们可以看到在子线程中是无法正常获取到父线程的线程数据的。

@Async实现父子线程之间的数据传递

多线程的情况下实现父子线程之间的数据传递方式有多种,这里我们介绍两种方式实现父子线程之间数据传递。

阿里巴巴TransmittableThreadLocal实现父子线程之间的用户数据传递


public class UserUtils {
    private static final ThreadLocal<String> userLocal = new TransmittableThreadLocal<>();

    public static String getUserId() {
        return userLocal.get();
    }

    public static void setUserId(String userId) {
        userLocal.set(userId);
    }

    public static void clear() {
        userLocal.remove();
    }

}

MDC实现线程池tranceId全链路传递

1:首先定义一个自定义线程池类继承ThreadPoolTaskExecutor


// 首先定义一个自定义线程池类继承ThreadPoolTaskExecutor
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolTaskExecutor {

    @Override
    public void execute(Runnable task) {
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}

2:编写tranceid包装工具类,判断当前线程对应MDC的上下文是否存在,存在则是子线程,设置MDC中的traceId值,不存在则生成新的tranceid,再执行run方法,执行结束之后清除线程tranceId


// 编写tranceid包装工具类
// 判断当前线程对应MDC的上下文是否存在,存在则是子线程,设置MDC中的traceId值
// 不存在则生成新的tranceid,再执行run方法,执行结束之后清除线程tranceId
public class ThreadMdcUtil {
    private final static String MDC_TRACE_ID = "traceId";

    public static void setTraceIdIfAbsent() {
        if (MDC.get(MDC_TRACE_ID) == null) {
            MDC.put(MDC_TRACE_ID, UUID.randomUUID().toString().replaceAll("-", ""));
        }
    }

    public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }

    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

3:使用ThreadPoolExecutorMdcWrapper改造自定义线程池



// 初始化自定义线程池
@Slf4j
@Configuration
@EnableConfigurationProperties({TaskExecutorProperties.class,ExportExecutorProperties.class})
public class ExecutePoolConfig {

    // 普通任务线程池配置
    private final TaskExecutorProperties taskExecutorProperties;

    // 导出任务线程池配置
    private final ExportExecutorProperties exportExecutorProperties;

    @Autowired
    public ExecutePoolConfig(TaskExecutorProperties taskExecutorProperties,ExportExecutorProperties exportExecutorProperties) {
        this.taskExecutorProperties = taskExecutorProperties;
        this.exportExecutorProperties = exportExecutorProperties;
    }

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        return buildExecutor(taskExecutorProperties);
    }
    @Bean("exportExecutor")
    public Executor exportExecutor() {
        return buildExecutor(exportExecutorProperties);
    }

    /**
     * 构建线程池
     *
     * @param executorProperties ExecutorBaseProperties
     * @return {@link Executor}
     */
    private Executor buildExecutor(ExecutorBaseProperties executorProperties) {

        ThreadPoolExecutorMdcWrapper executor = new ThreadPoolExecutorMdcWrapper();

        // 设置核心线程数
        executor.setCorePoolSize(executorProperties.getCorePoolSize());

        // 设置最大线程数
        executor.setMaxPoolSize(executorProperties.getMaxPoolSize());

        // 设置队列大小
        executor.setQueueCapacity(executorProperties.getQueueCapacity());

        // 设置线程池维护线程所允许的空闲时间
        executor.setKeepAliveSeconds(executorProperties.getKeepAliveSeconds());

        // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
        executor.setWaitForTasksToCompleteOnShutdown(true);

        // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁
        executor.setAwaitTerminationSeconds(30);

        // 设置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(executorProperties.getThreadNamePrefix());

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        RejectedExecutionHandler rejectedExecutionHandler = null;

        try {

            Class<?> clazz = Class
                .forName("java.util.concurrent.ThreadPoolExecutor$" + executorProperties.getRejectionPolicyName());
            rejectedExecutionHandler = (RejectedExecutionHandler)clazz.newInstance();

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            log.error("获取rejection-policy异常,请查看配置文件", e);
            return null;

        }

        executor.setRejectedExecutionHandler(rejectedExecutionHandler);
        // 执行初始化
        executor.initialize();
        //return TtlExecutors.getTtlExecutor(executor);
        return executor;
    }
}

4:执行结果


参考文章--MDC实现线程池tranceId全链路传递

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
@Async注解是用来表示方法是异步执行的。在引用中的代码示例中,@Async注解用于表示MyTask类中的两个方法doing()和doing2()是异步执行的。这意味着这两个方法将在后台线程中执行,而不会阻塞主线程的执行。这样可以在执行方法时延迟一段时间再执行,从而实现延迟执行的效果。例如,在doing()方法中,使用了@Scheduled注解设置了初始延迟1秒以及每隔3秒执行一次,而在doing2()方法中,使用了@Scheduled注解设置了初始延迟1秒以及每隔6秒执行一次。这样,可以实现方法的延迟执行功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [async延时函数](https://blog.csdn.net/weixin_51042892/article/details/123339420)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_blog_pc_vip","utm_medium":""}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [springboot2 定时任务:同步 异步 多线程 延迟执行,cron详解](https://blog.csdn.net/m0_37202351/article/details/82763303)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_blog_pc_vip","utm_medium":""}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [js的延迟执行问题分析](https://download.csdn.net/download/weixin_38560797/14822900)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_blog_pc_vip","utm_medium":""}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值