• 异步调用中的问题


    一、异步任务的定义

    异步任务是相对同步调用而言,同步是程序按照预定顺序一步一步的执行,而异步不需要等上一步程序执行完就可以执行,多线程是实现异步调用的一种方式,MQ 宏观上的一种异步;

    二、常用场景

    处理log,发送邮件、验证码,涉及到网络IO调用的操作

    三、使用方式

    springboot中异步任务的使用很简单,只需要在启动类上加上@EnableAsync, 然后在需要异步的方法里面加上 @Async;一般需要江宁异步任务单独写在一个类里面,不能异步方法和调用这个异步方法在同一个类里面;异步任务所在地类需要加上component 纳入IOC容器

    四、失效的场景

            1、异步方法不是public的;

            2、异步方法返回值只能是void或者future;

            3、异步方法使用static修饰会失效;

           4、spring无法扫描到异步类,即异步方法所在类没有加@component,

          5、调用方与被调用方在同一个类里面也会失效,即异步方法调用方与被调动方不能再同一个类里面

    五、异步调用原理

            spring在扫描bean的时候会扫描方法行是否有@Async注解,动态的生成一个子类即proxy代理类,当这个注解的方法被调用的时候,实际上是由代理类来调用,代理类在调用的时候增加异步作用。

           如果这个注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来哪个bean,所以会失效;

          一般要将异步方法单独抽成一个类,类中需要使用@autowired 或者@Resource等注解自动注入,不能手动new一个对象;

           在Async方法上加 @transactional 是没用的,但是在调用async方法的方法上加事务是有效的;

    六、直接使用异步任务的问题

    容易出现OMM内存溢出

    直接使用@Async 注解没有指定线程池的话,默认使用spring创建的ThreadPoolTaskExecutor,核心线程池数是8,最大线程池数是IntegerMAX_VALUE(21亿),队列使用的是LinkedBlockingQueue;空闲线程保存60s;拒绝策略是AbortPolicy,当请求过多线程都被阻塞的时候就容易OMM;

    解决方案:使用自定义线程池;

    1. @Configuration
    2. @EnableAsync
    3. public class ThreadPoolTaskConfig {
    4. @Bean("threadPoolTaskExecutor")
    5. public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
    6. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    7. //线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
    8. //如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
    9. executor.setCorePoolSize(16);
    10. //executor.setAllowCoreThreadTimeOut();
    11. //阻塞队列 当核心线程数达到最大时,新任务会放在队列中排队等待执行
    12. executor.setQueueCapacity(124);
    13. //最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
    14. //任务队列已满时, 且当线程数=maxPoolSize,,线程池会拒绝处理任务而抛出异常
    15. executor.setMaxPoolSize(64);
    16. //当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
    17. //允许线程空闲时间30秒,当maxPoolSize的线程在空闲时间到达的时候销毁
    18. //如果allowCoreThreadTimeout=true,则会直到线程数量=0
    19. executor.setKeepAliveSeconds(30);
    20. //spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
    21. //jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
    22. executor.setThreadNamePrefix("自定义线程池-");
    23. // rejection-policy:当pool已经达到max size的时候,如何处理新任务
    24. // CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
    25. // AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
    26. // DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
    27. // DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
    28. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    29. executor.initialize();
    30. return executor;
    31. }
    32. }

    使用的时候 由原来的 @Async 改为 @Async("threadPoolTaskExecutor");

    还存在的问题是阻塞队列中的线程还在内存,但是此时服务器宕机就会丢失;

  • 相关阅读:
    Qt之普通项目如何生成DLL(含源码+注释)
    110. 平衡二叉树
    MVC医院信息管理系统源码 BS架构
    sprintboot集成flink快速入门demo
    客厅、厨房、卫生间瓷砖怎么挑选,看完不再烦恼!福州中宅装饰,福州装修
    优化 Redis 集群缓存分配:解决节点间分配不均导致内存溢出问题
    【Dockerfile镜像实战】构建LNMP环境并运行Wordpress网站平台
    掘金夜谈-畅聊程序人生(笔记)
    【好诗选读】新春诗会作品合集|黄晓平 蒋德明 徐书遐|刘红立 孟萌 刘东宏
    访问学者申请|面签的八大注意事项
  • 原文地址:https://blog.csdn.net/weixin_45063957/article/details/126807051