Spring的@Async注解实现异步方法

fsta

@Async注解

Spring3开始提供了@Async注解,该注解可以标注在方法或者类上,从而可以方便的实现方法的异步调用。调用者在调用异步方法时将立即返回,方法的实际执行将提交给指定的线程池中的线程执行。

@Async注意事项:

  • @Async标注在类上时,表示该类的所有方法都是异步方法。
  • @Async注解的方法一定要通过依赖注入调用(因为要通过代理对象调用),不能直接通过this对象调用,否则不生效。

@Async使用示例

1、Spring中启用@Async

创建一个配置类,并加上@EnableAsync注解即可

@Configuration
@EnableAsync
public class SpringAsyncConfig{}

2、使用@Async

创建异步方法执行类

@Service
public class AsyncService {
    // 无返回值的异步方法
    @Async
    public void noReturnMethod() {
        String tName = Thread.currentThread().getName();
        System.out.println("current thread name : " + tName);
        System.out.println("noReturnMethod end");
    }

    // 有返回值的异步方法
    @Async
    public Future<String> withReturnMethod() {
        String tName = Thread.currentThread().getName();
        System.out.println("current thread name : " + tName);
        return new AsyncResult<>("aaa");
    }
}

创建调用异步方法的类

@RestController
@RequestMapping("/api/async/test/")
public class AsyncController {
    @Autowired
    AsyncService asyncService;

    // 无返回值
    @GetMapping("/noReturn")
    public String noReturn() {
        asyncService.noReturnMethod();
        return "success";
    }

    // 有返回值
    @GetMapping("/withReturn")
    public String withReturn() {
        Future<String> future = asyncService.withReturnMethod();
        try {
            String res = future.get();// 阻塞获取返回值
            System.out.println("res = " + res);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return "success";
    }
}

Spring定义的线程池类

Spring 已经定义的线程池类有如下一些:

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

配置自定义线程池

异步方法默认的线程池

在@EnableAsync注解中有如下注释说明:

By default, Spring will be searching for an associated thread pool definition:either a unique {@link org.springframework.core.task.TaskExecutor} bean in the context,or an {@link java.util.concurrent.Executor} bean named "taskExecutor" otherwise. Ifneither of the two is resolvable, a {@link org.springframework.core.task.SimpleAsyncTaskExecutor}will be used to process async method invocations.

翻译一下就是:

Spring首先会通过下面两种方式查找作为异步方法的默认线程池:
1、查找唯一的一个TaskExecutor类型的bean
2、或者是一个名称为“taskExecutor”的Executor类型的Bean。
如果上面两种方式都没有查找到,则使用SimpleAsyncTaskExecutor作为异步方法的默认线程池

而SimpleAsyncTaskExecutor线程池去执行@Async标注的异步方法,由于该线程池不会重用线程,所以项目中推荐使用自定义的线程池。

配置异步方法默认自定义线程池

配置@Async默认的线程池有多种方式:

  1. 重新实现接口AsyncConfigurer
  2. 继承AsyncConfigurerSupport
  3. 自定义一个TaskExecutor类型的bean。
  4. 自定义一个名称为“taskExecutor”的Executor类型的Bean。

下面展示了通过方式1(重新实现接口AsyncConfigurer)来配置默认的自定义线程池:

// 配置类实现AsyncConfigurer接口的getAsyncExecutor()方法
@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);//核心池大小
        executor.setMaxPoolSize(6);//最大线程数
        executor.setKeepAliveSeconds(60);//线程空闲时间
        executor.setQueueCapacity(10);//队列程度
        executor.setThreadNamePrefix("my-executor1-");//线程前缀名称
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
        executor.setAllowCoreThreadTimeOut(true);// 允许销毁核心线程
        executor.initialize();
        return executor;
    }
}

不同异步方法配置不同线程池

有时候不同功能的异步方法需要配置不同的线程池,可以通过在@Async上指定线程池的名称来实现

@Configuration
public class ExecutorConfig {
    @Bean("customExecutor-1")// 自定义线程池1
    public Executor customExecutor1() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);//核心池大小
        executor.setMaxPoolSize(6);//最大线程数
        executor.setKeepAliveSeconds(60);//线程空闲时间
        executor.setQueueCapacity(10);//队列程度
        executor.setThreadNamePrefix("customExecutor-1-");//线程前缀名称
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
        executor.setAllowCoreThreadTimeOut(true);// 允许销毁核心线程
        executor.initialize();
        return executor;
    }

    @Bean("customExecutor-2")// 自定义线程池2
    public Executor customExecutor2() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);//核心池大小
        executor.setMaxPoolSize(6);//最大线程数
        executor.setKeepAliveSeconds(60);//线程空闲时间
        executor.setQueueCapacity(10);//队列程度
        executor.setThreadNamePrefix("customExecutor-2-");//线程前缀名称
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());//配置拒绝策略
        executor.setAllowCoreThreadTimeOut(true);// 允许销毁核心线程
        executor.initialize();
        return executor;
    }
}
@Async("customExecutor-1")
public void method1(){}

@Async("customExecutor-2")
public void method2(){}

@Async的异常处理

当方法是带Future返回值的时候,Future.get()方法会抛出异常,所以异常捕获是没问题的。但是当方法是不带返回值的时候,那么此时主线程就不能捕获到异常,需要额外的配置来处理异常,可以有下面两种方式。

1、通过try-catch处理异常

直接在异步方法中使用try-catch来处理抛出的异常。这个方法也可以用于带Future返回值的异步方法。

2、通过实现AsyncUncaughtExceptionHandler接口

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        // 省略自定义线程池的代码
    }

    // 自定义异常处理
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                System.out.println(method.getName() + "发生异常!异常原因:" + throwable.getMessage() );
            }
        };
    }
}

参考资料

阅读 2.1k
8 声望
0 粉丝
0 条评论
8 声望
0 粉丝
宣传栏