SpringBoot中的异步处理框架@Async

本文详细介绍了SpringBoot中如何使用@Async进行异步处理,包括开启异步、服务定义、线程池配置,以及异步编程在用户注册场景的应用。重点讲解了线程池优化,展示了不同线程池策略,并列举了异步编程的实际应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringBoot中的异步处理框架@Async

1、分析

在SpringBoot的日常开发中,一般都是同步调用的,但经常有特殊业务需要做异步来处理。比如:注册用户、需要送积分、发短信和邮件、或者下单成功、发送消息等等。

  • 第一个原因:容错问题,如果送积分出现异常,不能因为送积分而导致用户注册失败。
  • 第二个原因:提升性能,比如注册用户花了30毫秒,送积分划分50毫秒,如果同步的话一共耗时:70毫秒,用异步的话,无需等待积分,故耗时是:30毫秒就完成了业务。

场景:用户注册 发送短信和添加积分

在这里插入图片描述

package com.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class RegController {

    // 注册
    @GetMapping("/reg")
    public String reguser(){
        // 1、注册用户
        log.info("新用户注册");
//        userService.save(user);

        // 2、发送短信
        log.info("新用户注册");
//        messageService.sendMsg();

        // 3、添加积分
        log.info("新用户注册");
//        scoreService.addScore(user);

        return "OK";
    }

}

2、什么是同步和异步

2.1 串行执行
// 注册
    @GetMapping("/reg")
    public String reguser(){
        // 1、注册用户
        log.info("新用户注册");
//        userService.save(user);

        // 2、发送短信
        log.info("新用户注册");
//        messageService.sendMsg();

        // 3、添加积分
        log.info("新用户注册");
//        scoreService.addScore(user);

        return "OK";
    }

在这里插入图片描述

串行执行时长:所有方法执行的总和

用户注册:50MS 短信发送:100ms 、添加积分:100ms

总时长:250ms 这个方法执行完毕。

2.2 异步执行

在这里插入图片描述

分析:执行用户注册的执行时长,并不会因为短信发送、添加积分受到影响。执行时间:>50ms

异步执行:方法与方法之间互不影响

3、异步编程–用户注册

3.1 让springboot框架支持异步处理
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync //开启异步执行
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
3.2 定义异步处理的注册service
package com.example.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class RegService {

    // 发送短信,用异步进行处理和标记
    @Async
    public void sendMsg(){
        // todo :模拟耗时5秒
        try {
            Thread.sleep(5000);
            log.info("---------------发送消息--------");
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    // 添加积分,用异步进行处理和标记
    @Async
    public void addScore(){
        // todo :模拟耗时5秒
        try {
            Thread.sleep(5000);
            log.info("---------------处理积分--------");
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}
3.3 调用异步处理
package com.example.controller;

import com.example.service.RegService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class RegController {

    @Autowired
    private RegService regService;

    // 注册
    @GetMapping("/reg")
    public String reguser(){
        // 1、注册用户
        log.info("新用户注册");
//        userService.save(user);

        // 2、发送短信
        log.info("新用户注册");
//        messageService.sendMsg();
        regService.sendMsg();

        // 3、添加积分
        log.info("新用户注册");
//        scoreService.addScore(user);
        regService.addScore();

        return "OK";
    }

}
3.4 执行结果

执行的时间很快,因为添加积分和发送短信都是额外的开启了新的线程去执行。所有异步编程的性能是很快的。

3.5 小结

在SpringBoot的日常开发中,一般都是同步调用的,但经常有特殊业务需要做异步来处理。比如:注册用户、需要送积分、发短信和邮件、或者下单成功、发送消息等等。

  • 第一个原因:容错问题,如果送积分出现异常,不能因为送积分而导致用户注册失败。
  • 第二个原因:提升性能,比如注册用户花了30毫秒,送积分划分50毫秒,如果同步的话一共耗时:70毫秒,用异步的话,无需等待积分,故耗时是:30毫秒就完成了业务。

4、异步线程池的优化

Springboot的tomcat的线程默认数量:200个,如果异步线程线程过多,有请求线程、异步处理的线程时,这些线程都在争抢CPU的执行时间,很耗费资源 ,因为@Async(https://github.com/Async)注解默认情况下用的是SimpleAsyncTaskExecutor线程池。该线程池不是真正意义上的线程

因为线程不重用,每次调用都会新建一个新的线程。

通过上面的日志分析获得结论:【task-1】,【task-2】,【task-3】….递增。

在这里插入图片描述

@Async注解异步框架提供多种线程机制:

  • SimpleAsyncTaskExecutor:简单的线程池,不重用线程,每次调用都会创建一个新的线程。
  • SyncTaskExecutor:没实现异步调用,只是一个同步操作,只适合用于不需要多线程的地方。
  • ConcurrentTaskExecutor:Executor的适配类,不推荐使用。
  • ThreadPoolTaskScheduler:可以和cron表达式使用。
  • ThreadPoolTaskExecutor:最常用,推荐,其本质是java.util.concurrent.ThreadPoolExecutor的包装
package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

@Configuration
public class SyncThreadPoolConfiguration {
    /**
     * 把springboot中的默认的异步线程线程池给覆盖掉。用ThreadPoolTaskExecutor来进行处理
     **/

    @Bean(name="threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor getThreadPoolTaskExecutor(){
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 1: 创建核心线程数 cpu核数 -- 50
        threadPoolTaskExecutor.setCorePoolSize(10);
        // 2:线程池维护线程的最大数量,只有在缓存队列满了之后才会申请超过核心线程数的线程
        threadPoolTaskExecutor.setMaxPoolSize(100);
        // 3:缓存队列 可以写大一点无非就浪费一点内存空间
        threadPoolTaskExecutor.setQueueCapacity(200);
        // 4:线程的空闲事件,当超过了核心线程数之外的线程在达到指定的空闲时间会被销毁 200ms
        threadPoolTaskExecutor.setKeepAliveSeconds(200);
        // 5:异步方法内部线的名称
        threadPoolTaskExecutor.setThreadNamePrefix("example-thread-");
        // 6:缓存队列的策略  多线程 JUC并发
        /* 当线程的任务缓存队列已满并且线程池中的线程数量已经达到了最大连接数,如果还有任务来就会采取拒绝策略,
         * 通常有四种策略:
         *ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出异常:RejectedExcutionException异常
         *ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
         *ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
         *ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功。
         *ThreadPoolExecutor. 扩展重试3次,如果3次都不成功再移除。
         *jmeter 压力测试 1s=500
         * */
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

5、异步应用场景

大部分开发中,还是串行执行。除非在开发过程中,一个业务和另外一个业务的关联性不是强耦合,执行失败或者成功都不影响它核心业务。可以把这些附属业务剥离处理用异步执行。比如:用户注册:发送短信,发送邮件,下单成功发送短信,发送微信登等

异步编程的框架:消息中间件(ActiveMQ、RabbitMQ)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫拾壹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值