Async 使用详解

news2024/12/23 13:23:12

Spring Boot异步调用@Async

在实际开发中,有时候为了及时处理请求和进行响应,我们可能会多任务同时执行,或者先处理主任务,也就是异步调用,异步调用的实现有很多,例如多线程、定时任务、消息队列等,

一、普通串行执行演示

1.1任务类

假设有三个任务需要处理我们在平时开发中,会按照逻辑顺序依次编写代码;

@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(5000));
        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) + "毫秒");
    }
}

1.2 测试类

@SpringBootTest
class ApplicationTests {

    @Test
    void contextLoads() {
    }
    @Autowired
    private Task task;

    @Test
    public void test() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }
}

1.3运行结果及分析

观察结果发现,先调用的任务一,任务一执行完成才能开始任务二,任务二完成才能执行任务三;这种串行话的执行程序在实际开发中并没以后任务问题
在这里插入图片描述
假如有这样一个功能,任务二是发送邮件,任务三是发送短信,实际开发中这两个任务互补干扰;谁先谁后都都可以,如果是串行操作是不是有点效率低了!最好的办法可以让这两个任务并行操作
实现异步的方式:

  • 1、使用多线程,在主线程中分别给任务一,任务二另起一个线程来执行任务
  • 2、使用@Async注解实现异步

二、@Async使用演示

2.1 在启动类上添加@EnableAsync开启异步

@AsyncSpring内置注解,用来处理异步任务,在SpringBoot中同样适用,且在SpringBoot项目中,除了boot本身的starter外,不需要额外引入依赖。
而要使用@Async,需要在 启动类上加上@EnableAsync主动声明来开启异步方法。

@SpringBootApplication
@EnableAsync
public class Application {

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

}

2.2 编写任务类,在任务方法添加@Async

@Component
public class TaskAsync {
    public static Random random = new Random();

    @Async
    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) + "毫秒");
    }

    @Async
    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) + "毫秒");
    }

    @Async
    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.3测试类

@SpringBootTest
class ApplicationTests {

    @Test
    void contextLoads() {
    }
    @Autowired
    private Task task;
    @Autowired
    private TaskAsync taskAsync;

    @Test
    public void test() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }

    @Test
    public void taskAsynctest() throws Exception {
        try {
            long start = System.currentTimeMillis();
            taskAsync.doTaskOne();
            taskAsync.doTaskTwo();
            taskAsync.doTaskThree();
            Thread.sleep(10000);
            long end = System.currentTimeMillis();
            System.out.println("end = " + (end - start)/1000f);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
}

2.3 多次运行观察结果

然后我们在运行测试类,这个时候输出可能就五花八门了,任意任务都可能先执行完成,也有可能有的方法因为主程序关闭而没有输出。
在这里插入图片描述

2.4总结

使用 @Async可以实现程序的异步执行,完成程序优化;但实际使用中需要注意异步失效问题:
被调用方法和调用方法处理同一个类中会导致异步失效

  • 同一个类中。 失效的代码
class TestService {
    void a() { 
      this.b();
    }
	
    @Async
    void b(){}
}
  • 正常的代码
class TestService {
    void a(){ 
       BService.b();
    }
}

class BService() {
    @Async
    void b(){}
}

从@Async案例找到Spring框架的bug:exposeProxy=true不生效原因大剖析+最佳解决方案

三、@Async + 线程池使用

@Async 异步方法默认使用 Spring 创建 ThreadPoolTaskExecutor (参考TaskExecutionAutoConfiguration),其中默认核心线程数为 8,默认最大队列和默认最大线程数都是 Integer.MAX_VALUE。创建新线程的条件是队列填满时,而这样的配置队列永远不会填满,如果有 @Async 注解标注的方法长期占用线程 (比如 HTTP 长连接等待获取结果),在核心 8 个线程数占用满了之后,新的调用就会进入队列,外部表现为没有执行。

我们可以自定义一个线程池,线程数的设定需要考虑一下要执行的任务是 IO 密集型任务,还是 CPU 密集型任务。对于 CPU 密集型任务,如 CPU 核数 + 1;对于 IO 密集型任务,由于 IO 密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 CPU 核数 * 2。

3.1线程池配置类

接下来给出一个 IO 密集型任务的线程池配置代码


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 ThreadPoolConfig {

    /**
     * 核心线程数
     */
    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;

    /**
     * 最大线程数
     */
    private static final int MAX_POOL_SIZE = CORE_POOL_SIZE * 4 < 256 ? 256 : CORE_POOL_SIZE * 4;

    /**
     * 允许线程空闲时间(单位为秒)
     */
    private static final int KEEP_ALIVE_TIME = 10;

    /**
     * 缓冲队列数
     */
    private static final int QUEUE_CAPACITY = 200;

    /**
     * 线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁
     */
    private static final int AWAIT_TERMINATION = 60;

    /**
     * 用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
     */
    private static final Boolean WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN = true;

    /**
     * 线程池名前缀
     */
    private static final String THREAD_NAME_PREFIX = "Spider-ThreadPool-";


    @Bean("spiderTaskExecutor")
    public ThreadPoolTaskExecutor spiderTaskExecutor () {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setKeepAliveSeconds(KEEP_ALIVE_TIME);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        taskExecutor.setWaitForTasksToCompleteOnShutdown(WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN);
        taskExecutor.setAwaitTerminationSeconds(AWAIT_TERMINATION);
        /**
         * 拒绝策略 => 当pool已经达到max size的时候,如何处理新任务
         * CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
         * AbortPolicy:直接抛出异常,这是默认策略;
         * DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
         * DiscardPolicy:直接丢弃任务;
         */
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }

}

3.2 修改任务类

在任务类中的方法上添加@Async("spiderTaskExecutor")

@Component
public class TaskAsync {
    public static Random random = new Random();

    @Async("spiderTaskExecutor")
    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) + "毫秒");
    }

    @Async("spiderTaskExecutor")
    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) + "毫秒");
    }

    @Async("spiderTaskExecutor")
    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) + "毫秒");
    }
}

最后补充一些知识,要合理的控制线程数(比如采集订单信息的同时要采集订单详情和文章信息,订单详情和文章信息可以合并在一个线程中处理),不要滥用。需要考虑什么时候使用 MQ,什么时候开启线程异步处理。推荐一个分析 jstack 文件的工具,IBM Thread and Monitor Dump Analyzer for Java,分析一下正在运行、发生死锁、等待、阻塞的线程。

四 获取异步执行结果

上面演示了@Async,但是有时候除了需要任务并发调度外,我们还需要获取任务的返回值,且在多任务都执行完成后再结束主任务,这个时候又该怎么处理呢?
CompletableFuture使用详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/601460.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

若依框架快速搭建(二)

目录 数据库设计功能模块设计XXX信息管理xxx查询xxx添加xxx删除xxx修改xxx导出 功能模块实现运行数据库自动代码生成在IDEA中找到RuoYi-generator&#xff0c;修改配置运行前后端项目&#xff0c;在网页中找到代码生成模块导入表后点击确定&#xff0c;序号前打勾&#xff0c;再…

Mac - 光标特效 By CursorEffect2

目录 一.引言 二.安装 CursorEffect2 三.使用 CursorEffect2 四.使用效果 五.内存消耗 六.一键关闭 七.总结 一.引言 在自己搭建的 Hexo 博客上可以定义鼠标点击的特效&#xff0c;如图点击后可以产生彩色的斑点。 于是想着除了浏览 Hexo 博客外&#xff0c;能不能别的也…

【笔试强训编程题】Day1.(组队竞赛100449)和(删除公共字符69390)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训编程题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;! 文章目录…

【CSS3系列】第一章 · CSS3新增的三种基本属性

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

FineBI6.0基础学习第一课 数据门户

PC端门户使用示例 首先,以管理员身份登录FineBI系统,安装数据门户,安装步骤见官网 新建一个数据门户

SouapUI接口测试之创建性能测试

SouapUI也是一个能生动的体现一个系统&#xff08;项目&#xff09;性能状态的工具&#xff0c;本篇就来说说如何在SouapUI工具下创建性能测试 一、创建测试用例 由于在《SouapUI接口测试之使用Excel进行参数化》篇已经创建好了测试用例&#xff0c;本篇就不讲解如何创建测试…

SpringCloudAlibaba:服务网关之Gateway学习

目录 一、网关简介 &#xff08;一&#xff09;为什么要用网关 &#xff08;二&#xff09;网关解决了什么问题 &#xff08;三&#xff09;常用的网关 二、Gateway简介 &#xff08;一&#xff09;核心概念 &#xff08;二&#xff09;工作原理 三、Gateway快速入门 &…

linuxOPS基础_用户与组管理

linux用户与组概念 为什么需要了解用户和组 服务器要添加多账户的作用 ​ 针对不同用户分配不同的权限&#xff0c;不同权限可以限制用户可以访问到的系统资源 ​ 提高系统的安全性 ​ 帮助系统管理员对使用系统的用户进行跟踪 用户和组的关系 理论上Linux系统中的每个用户…

2023年6月实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先&#xff0c;来看下效果图 在线体验地址&#xff1a;https://geojson.hxkj.vip&#xff0c;并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

日本原装Yokogawa AQ6317B横河AQ6317C光谱分析仪

Yokogawa AQ6317B光谱分析仪&#xff0c;50 GHz ​Yokogawa AQ6317B 光谱分析仪 (OSA) 是一款先进的光谱分析仪&#xff0c;应用范围广泛&#xff0c;包括光源评估、光学设备损耗波长特性的测量以及 WDM&#xff08;波分复用&#xff09;系统的波形分析。在 Yokogawa 购买产品…

第十七篇、基于Arduino uno,获取cp2d12红外测距传感器的原始值和距离值——结果导向

0、结果 说明&#xff1a;先来看看串口调试助手显示的结果&#xff0c;第一个值是原始的模拟电压值&#xff0c;第二个值是距离值&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;虽然红外测距传感器形态各异&#xff0c;但是原理和代码都是…

java中实现对象属性复制的工具类

在 Java 中&#xff0c;有多个工具类可用于实现对象属性的复制&#xff0c;使得属性值从一个对象复制到另一个对象。以下是几个常用的工具类&#xff1a; Apache Commons BeanUtils&#xff1a; Apache Commons BeanUtils 提供了 BeanUtils 类&#xff0c;可以方便地进行属性…

一文简介Linux固件子系统的实现机制

一、Linux固件子系统概述 固件是硬件设备自身执行的一段程序。固件一般存放在设备flash内。而出于成本和便利性的考虑&#xff0c;通常是先将硬件设备的运行程序打包为一个特定格式的固件文件&#xff0c;存储到终端系统内&#xff0c;通过终端系统给硬件设备进行升级。Linux内…

java面向对象学习

一、Java类及类的成员 1.类是对一类事物的描述&#xff0c;是抽象的、概念上的定义 2.对象是实际存在的该类事物的每个个体&#xff0c;因而也称为实例 3.属性&#xff1a;对应类中的成员变量 4.行为&#xff1a;对应类中的方法 权限修饰符号&#xff1a;public、protected…

玄派玄智星笔记本U盘重装电脑系统详细步骤教学

玄派玄智星笔记本U盘重装电脑系统详细步骤教学。有用户使用玄派玄智星笔记本的时候&#xff0c;电脑系统出现了故障&#xff0c;导致自己无法启动电脑了。这个情况需要使用U盘去进行系统的重装&#xff0c;那么具体要怎么去进行重装呢&#xff1f;来看看以下的操作方法吧。 准备…

移动端布局之流式布局1(百分比布局):流式布局基础、案例:京东移动端首页1

移动端布局之流式布局1 流式布局&#xff08;百分比布局&#xff09;基础案例&#xff1a;京东移动端首页搭建相关文件夹结构设置视口标签以及引入初始化样式normalize.css引入我们的css初始化文件与首页css body设置index.css app布局和app内容填充index.htmlindex.css 搜索模…

小说App源码分享,从零开始搭建小说阅读平台

作为一名小说阅读爱好者或者创业者&#xff0c;你是否也曾经想要搭建自己的小说阅读平台&#xff1f;然而&#xff0c;开发一款小说App通常需要大量的人力、物力和时间成本&#xff0c;怎样才能让它变得更加容易&#xff1f;今天&#xff0c;我将与大家分享如何从零开始&#x…

VSD?啥是VSD?VSD应用场景你知道吗?

软件介绍 Vayo-Stencil Designer Vayo-Stencil Designer&#xff08;简称VSD&#xff09;是一款面向企业的专业钢网设计软件&#xff0c;可以为企业高效构建适合企业自身产品和工艺know-how的数字化开口规范&#xff0c;解决钢网开口审查、局部开口设计、完整钢网设计、PIP焊…

07 【内置指令 自定义指令】

1. 内置指令 之前学过的指令&#xff1a; v-bind 单向绑定解析表达式&#xff0c;可简写为 :v-model 双向数据绑定v-for 遍历数组 / 对象 / 字符串v-on 绑定事件监听&#xff0c;可简写为****v-show 条件渲染 (动态控制节点是否展示)v-if 条件渲染&#xff08;动态控制节点是…

一文读懂责任分配矩阵,解决你80%的项目难题

成功的项目管理取决于整个团队对角色和职责的理解&#xff0c;使用责任分配矩阵分配和定义角色是使项目保持在正轨并为成功做好准备的好方法。 如果设计得当&#xff0c;责任分配矩阵能够促进项目的成功交付。 一、什么是责任分配矩阵 责任分配&#xff08;RACI&#xff09;矩…