Spring异步处理——@Async注解

一、简介

@Async是spring框架中十分好用的异步写法:

1,在方法上使用该@Async注解,申明该方法是一个异步任务;

2,在类上面使用该@Async注解,申明该类中的所有方法都是异步任务;

3,使用此注解的方法的类对象,必须是spring管理下的bean对象;

4,要想使用异步任务,需要在主类上开启异步配置,即,配置上@EnableAsync注解

@Async注解工作原理
就是在调用被注解的方法时,Spring会将该方法的执行转移到线程池中的一个线程进行处理。执行完成后,方法的返回值将通过Future或CompletableFuture进行封装,以便获取方法的返回结果。

@Async 注解适用于以下场景,并具有以下优势:

  • 网络请求:在处理网络请求时,可以使用 @Async 注解将请求发送和响应处理分离,提高系统的并发处理能力。
  • 耗时计算:对于需要耗费大量时间的计算任务,可以使用 @Async 注解将计算过程放在后台执行,避免阻塞主线程,提高系统的响应速度。
  • 并行处理:通过 @Async 注解,可以同时执行多个任务,将多个相互独立的任务并行处理,从而减少整体处理时间。
  • 响应能力提升:使用异步编程可以避免阻塞主线程,提高系统的并发能力和响应能力,增强用户体验。
  • 代码简化:使用 @Async 注解可以简化编程模型,将异步执行的逻辑与业务逻辑分离,使代码更清晰、易于维护。

异步执行通过将任务分解为多个并发执行的子任务,可以充分利用系统资源,提高系统的吞吐量和并发处理能力,从而提升系统的性能和响应能力。@Async 注解简化了异步编程的实现,使开发人员能够更方便地使用异步处理机制。同时,它还可以使代码更易于阅读和维护,提高开发效率。

二、 同步与异步理解

1、**同步:**同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果
2、异步:异步调用则是只是发送调用的指令,调用者无需等待被调用的方法完全执行完毕;而是继续执行下面的流程。
**例如:**在某个调用中,需要顺序调用 A, B, C三个过程方法;如他们都是同步调用,则需要将他们都顺序执行完毕之后,方算作过程执行完毕; 如B为一个异步的调用方法,则在执行完A之后,调用B,并不等待B完成,而是执行开始调用C,待C执行完毕之后,就意味着这个过程执行完毕了。在Java中,一般在处理类似的场景之时,都是基于创建独立的线程去完成相应的异步调用逻辑,通过主线程和不同的业务子线程之间的执行流程,从而在启动独立的线程之后,主线程继续执行而不会产生停滞等待的情况。

1、同步调用

下面通过一个简单示例来直观的理解什么是同步调用:
1、定义Task类,创建三个处理函数分别模拟三个执行任务的操作,操作消耗时间随机取(10秒内)

@Component
public class Task {
 
    public static Random random =new Random();
 
    public void doTaskOne() throws Exception {
        System.out.println("开始做任务一");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }
 
    public void doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }
 
    public void doTaskThree() throws Exception {
        System.out.println("开始做任务三");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
 
}

2、在单元测试用例中,注入Task对象,并在测试用例中执行doTaskOne、doTaskTwo、doTaskThree三个函数。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
public class ApplicationTests {
 
    @Autowired
    private Task task;
 
    @Test
    public void test() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }
 
}

3、执行单元测试,可以看到类似如下输出:
开始做任务一
完成任务一,耗时:4256毫秒

开始做任务二
完成任务二,耗时:4957毫秒

开始做任务三
完成任务三,耗时:7173毫秒

任务一、任务二、任务三顺序的执行完了,换言之doTaskOne、doTaskTwo、doTaskThree三个函数顺序的执行完成。

2、 异步调用

上述的同步调用虽然顺利的执行完了三个任务,但是可以看到执行时间比较长,若这三个任务本身之间不存在依赖关系,可以并发执行的话,同步调用在执行效率方面就比较差,可以考虑通过异步调用的方式来并发执行。
在Spring Boot中,我们只需要通过使用**@Async注解**就能简单的将原来的同步函数变为异步函数,Task类改在为如下模式:

    public static Random random = new Random();
 
    @Async
    public Future<String> doTaskOne() throws Exception {
        System.out.println("开始做任务1");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务1,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("任务1完成");
    }
 
    @Async
    public Future<String> doTaskTwo() throws Exception {
        System.out.println("开始做任务2");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务2,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("任务2完成");
    }
 
    @Async
    public Future<String> doTaskThree() throws Exception {
        System.out.println("开始做任务3");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        System.out.println("完成任务3,耗时:" + (end - start) + "毫秒");
        return new AsyncResult<>("任务3完成");
    }

看看我们做了哪些改变:

1、在测试用例一开始记录开始时间
2、在调用三个异步函数的时候,返回Future类型的结果对象
3、在调用完三个异步函数之后,开启一个循环,根据返回的Future对象来判断三个异步函数是否都结束了。若都结束,就结束循环;若没有都结束,就等1秒后再判断

开始做任务一
开始做任务二
开始做任务三
完成任务三,耗时:37毫秒
完成任务二,耗时:3661毫秒
完成任务一,耗时:7149毫秒
任务全部完成,总耗时:8025毫秒

可以看到,通过异步调用,让任务一、二、三并发执行,有效的减少了程序的总运行时间

三、@Async的使用

1、使用@Async注解时要先在启动类上添加@EnableAsync注解

 
// 基于Java配置的启用方式:
@Configuration  
@EnableAsync  
public class SpringAsyncConfig { ... }  
 
// Spring boot启用:
@EnableAsync
@EnableTransactionManagement
public class SettlementApplication {
    public static void main(String[] args) {
        SpringApplication.run(SettlementApplication.class, args);
    }
}

2、添加@Async注解
1)无返回值的调用方法

/**
    * 带参数的异步调用 异步方法可以传入参数
    *  对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉
    * @param s
    */
   @Async
   public void asyncInvokeWithException(String s) {
       log.info("asyncInvokeWithParameter, parementer={}", s);
       throw new IllegalArgumentException(s);
   }

2)Future返回值

 @Async("MyExecutor")
public Future<Map<Long, List>> queryMap(List ids) {
    List<> result = businessService.queryMap(ids);
    ..............
    Map<Long, List> resultMap = Maps.newHashMap();
    ...
    return new AsyncResult<>(resultMap);
}

四、@Async的线程池问题

1、Spring默认线程池

1、@Async注解在使用时,如果不指定线程池的名称,则使用spring默认的线程池,spring默认的线程池为SimpleAsyncTaskExecutor
2、方法上一旦标记了这个@Async注解,当其它线程调用这个方法时,就会开启一个新的子线程去异步处理该业务逻辑。

spring的默认线程池配置为
 
    默认核心线程数:8
    
    最大线程数:Integet.MAX_VALUE
 
    队列使用LinkedBlockingQueue
 
    容量是:Integet.MAX_VALUE
 
    空闲线程保留时间:60s
 
    线程池拒绝策略:AbortPolicy

可以看到,他对最大线程数没有限制,也就是会无限多的创建线程

2、自定义线程池

1、编写配置文件

spring:
  task:
    execution:
      pool:
        max-size: 6
        core-size: 3
        keep-alive: 3s
        queue-capacity: 1000
        thread-name-prefix: name

2、编写配置类

@Configuration
@Data
public class ExecutorConfig{
    /**
     * 核心线程
     */
    private int corePoolSize;
    /**
     * 最大线程
     */
    private int maxPoolSize;
    /**
     * 队列容量
     */
    private int queueCapacity;
    /**
     * 保持时间
     */
    private int keepAliveSeconds;
    /**
     * 名称前缀
     */
    private String preFix;
 
    @Bean("MyExecutor")
    public Executor myExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(preFix);
        executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

3、使用@Async注解并指定自定义线程池

 @Component
public class MyAsyncTask {
     @Async("MyExecutor") //使用自定义的线程池(执行器)
    public void asyncCpsItemImportTask(Long platformId, String jsonList){
        //...具体业务逻辑
    }
}

五、@Async的事务与异常处理

1.由于@Async注解由于是异步执行的,在其进行数据库的操作之时,外层的事务控制将无法进行事务管理。可以把@Transactional注解放到内部的需要进行事务的方法上,来实现事务管理

2.同样由于异步的关系,@Async方法中的抛出的异常无法被外部的try…catch捕获,因此要把try…catch写到异步方法内

你可能感兴趣的:(spring,java,服务器)