【异步注解@Async使用及其部分调优】

对于spring异步注解@Async的使用:

对于异步方法调用,从Spring3开始提供了@Async注解,该注解可以被标注在方法上,以便异步地调用该方法。调用者将在调用时立即返回,方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。

我们尝试一次异步操作。

定义接口,我们采用有回调的future。

public interface AsyncService {

    //涉及一个future的通道,以及一个计数使用的latch
    Future<Integer> getFollowed(Integer times, CountDownLatch latch);

    Future<Integer>  getAtention(Integer times, CountDownLatch latch);

}

进行方法实现

其中一个我使用了默认线程池,另外一个指定自己的线程池稍后介绍区别

@Service
public class AsyncServiceImpl implements AsyncService {


    @Override
    @Asyncpublic Future<Integer> getFollowed(Integer times, CountDownLatch latch) {
        Integer count = 0;
        //负责相关使用逻辑
        for (int i = 0; i < times; i++) {
            count += i;
        }

        latch.countDown();
        return new AsyncResult<>(new Integer(count));
    }

    @Override
    @Async("asyncExecutor")
    public Future<Integer> getAtention(Integer times, CountDownLatch latch) {
        Integer count = 0;
        //负责相关使用逻辑
        for (int i = 0; i < times; i++) {
            count += i;
        }
        latch.countDown();
        return new AsyncResult<>(new Integer(count));
    }

在外部设置好计数器返回现有结果

    long s = System.currentTimeMillis();
        //计数
        CountDownLatch latch = new CountDownLatch(2);
        Future<Integer> atention = asyncService.getAtention(times,latch);
        Future<Integer> followed = asyncService.getFollowed(times, latch);
        long e = System.currentTimeMillis();
        System.out.println("asyncService spend: " + (e - s));

定义自己异步线程池

@Configuration
public class AsyncThreadingConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //最小线程数
        taskExecutor.setCorePoolSize(5);
        //最大线程数
        taskExecutor.setMaxPoolSize(10);
        //等待队列
        taskExecutor.setQueueCapacity(25);
        // 允许线程空闲时间(单位:默认为秒)
        taskExecutor.setKeepAliveSeconds(30);
        // 线程池名前缀
        taskExecutor.setThreadNamePrefix("asyncExecutor-");
        //设置拒绝策略
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //等待所有任务结束后再关闭线程池
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
    }

}

基本上就是异步实现的过程。

首先我们Spring 已经实现的线程池

1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。
2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。
3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。
5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装。

Spring应用默认的线程池,指在@Async注解在使用时,不指定线程池的名称。查看源码,@Async的默认线程池为SimpleAsyncTaskExecutor

几种线程池会出现的问题:

在线程池应用中,参考阿里巴巴java开发规范:线程池不允许使用Executors去创建,不允许使用系统默认的线程池,推荐通过ThreadPoolExecutor的方式,这样的处理方式让开发的工程师更加明确线程池的运行规则,规避资源耗尽的风险。Executors各个方法的弊端:

newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

newCachedThreadPool和newScheduledThreadPool:要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

@Async默认异步配置使用的是SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误。针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0时开启限流机制,默认关闭限流机制即concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。

 @Async应用自定义线程池(第四步)

自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。在设置系统自定义线程池代替默认线程池时,虽可通过多种模式设置,但替换默认线程池最终产生的线程池有且只能设置一个(不能设置多个类继承AsyncConfigurer)。自定义线程池有如下模式:

  • 重新实现接口AsyncConfigurer
  • 继承AsyncConfigurerSupport
  • 配置由自定义的TaskExecutor替代内置的任务执行器

通过查看Spring源码关于@Async的默认调用规则,会优先查询源码中实现AsyncConfigurer这个接口的类,实现这个接口的类为AsyncConfigurerSupport。但默认配置的线程池和异步处理方法均为空,所以,无论是继承或者重新实现接口,都需指定一个线程池。且重新实现 public Executor getAsyncExecutor()方法。

Async可能会存在失效

没有过去到代理类,本类调用时,直接自己内部调用,没有走代理类

1.没有在@SpringBootApplication启动类当中添加注解@EnableAsync注解。

2.异步方法使用注解@Async的返回值只能为void或者Future。

3.没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值