SpringBoot 整合@Async实现异步

什么是异步调用?

异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。

如何实现异步调用?

多线程,这是很多人第一眼想到的关键词,没错,多线程就是一种实现异步调用的方式。

在非spring目项目中我们要实现异步调用的就是使用多线程方式,可以自己实现Runable接口或者集成Thread类,或者使用jdk1.5以上提供了的Executors线程池。

StrngBoot中则提供了很方便的方式执行异步调用:
1、启动类:添加@EnableAsync注解
2、只需在需要异步执行方法上添加@Async注解

例子:
首先不使用移步操作,先同步执行各个方法:

@Override
public String task1() throws InterruptedException {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return "1返回";
}

@Override
public String task2() throws InterruptedException {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
return "2返回" ;
}

@Override
public String task3() throws InterruptedException {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
return "3返回" ;
}
@RequestMapping("/myTask")
public String task1() throws Exception{
long start = System.currentTimeMillis();
String str1 = repairService.task1();
String str2 = repairService.task2();
String str3 = repairService.task3();
long end = System.currentTimeMillis();
System.out.println("任务返回值:" + str1 + str2 + str3 + ",任务总耗时:"+(end - start)+"毫秒");
return"task";
}

@Async
@Override
public Future<String> task1() throws InterruptedException {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("1返回") ;
}

上述,方法1,方法2,方法3执行的时间分别为:
完成任务一,耗时:599毫秒
完成任务三,耗时:720毫秒
完成任务二,耗时:791毫秒
总的执行时间为:2150毫米,可以看到三个小方法顺序执行,这样总的执行时间就大于等于 599 + 720 + 791 毫秒。

----------------------------------------------------------------------以下是异步操作-----------------------------------------------------------------

@Async
@Override
public Future<String> task2() throws InterruptedException {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("2返回") ;
}

@Async
@Override
public Future<String> task3() throws InterruptedException {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
return new AsyncResult<>("3返回") ;
}

long start = System.currentTimeMillis();
Future<String> str1 = repairService.task1();
Future<String> str2 = repairService.task2();
Future<String> str3 = repairService.task3();
long end = System.currentTimeMillis();
System.out.println("任务返回值:" + str1.get() + str2.get() + str3.get() + ",任务总耗时:"+(end - start)+"毫秒");
return"task";

上述,方法1,方法2,方法3执行的时间分别为:
完成任务一,耗时:599毫秒
完成任务三,耗时:720毫秒
完成任务二,耗时:791毫秒
总的执行时间为:801毫米,可以看到方法总的执行时间和三个小任务中最大的执行时间差不多,实现了异常操作,减少了了方法的执行总时间,提高了效率。

备注:
什么是 Future类型?

Future是对于具体的 Runnable或者 Callable任务的执行结果进行取消、查询是否完成、获取结果的接口。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

future方法:


   boolean cancel(boolean mayInterruptIfRunning);

   boolean isCancelled();

   boolean isDone();

   V get() throws InterruptedException, ExecutionException;

   V get(long timeout, TimeUnit unit) // futureResult.get(5, TimeUnit.SECONDS);

它声明这样的五个方法:

cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。

isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

isDone方法表示任务是否已经完成,若任务完成,则返回true;

get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

也就是说Future提供了三种功能:

判断任务是否完成;

能够中断任务;

能够获取任务执行结果。

我们在get方法中还定义了该线程执行的超时时间,通过执行这个测试我们可以观察到执行时间超过5秒的时候,这里会抛出超时异常,该执行线程就能够因执行超时而释放回线程池,不至于一直阻塞而占用资源。


一、实现多线程的方法:
1、继承thread类
2、实现runnable接口
3、使用Executor框架
二、线程同步机制
1、使用volatile实现轻量级的同步机制
2、使用synchronized
3、使用lock
三、线程执行顺序
使用join,thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
t.join(); //使调用线程 t 在此之前执行完毕。
t.join(1000); //等待 t 线程,等待时间是1000毫秒


springboot的线程池配置
创建一个配置类ExecutorConfig,用来定义如何创建一个ThreadPoolTaskExecutor,要使用@Configuration和@EnableAsync这两个注解,表示这是个配置类,并且是线程池的配置类,如下所示:

@Configuration
@EnableAsync
public class ExecutorConfig {
    private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);
    @Bean
    public Executor asyncServiceExecutor() {
        logger.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(5);
        //配置最大线程数
        executor.setMaxPoolSize(5);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-");

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }
}

注意,上面的方法名称为asyncServiceExecutor,稍后马上用到;
将Service层的服务异步化
打开AsyncServiceImpl.java,在executeAsync方法上增加注解@Async(“asyncServiceExecutor”),asyncServiceExecutor是前面ExecutorConfig.java中的方法名,表明executeAsync方法进入的线程池是asyncServiceExecutor方法创建的,如下:

@Override
    @Async("asyncServiceExecutor")
    public void executeAsync() {
        logger.info("start executeAsync");
        try{
            Thread.sleep(1000);
        }catch(Exception e){
            e.printStackTrace();
        }
        logger.info("end executeAsync");
    }

随着任务数量的增加,会增加活跃线程数

当活跃的线程数 = 核心线程数,测试不再增加活动线程数,而是

往任务队列里堆积

当任务队列满了,随着任务数量的增加,会在核心线程数的基础上开

线程

当活跃线程数 = 最大线程数,就不能增加线程了

此时可以继续新增任务

直到任务总数 > 最大啊线程数 + 队列长度,此时如果继续添加任务,会抛出RejectExecurionexception,拒绝任务

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值