Spring 框架——@Async 注解

news2024/11/22 16:56:43

目录

  • 1.同步调用与异步调用
    • 1.1.同步调用
    • 1.2.异步调用
    • 1.3.总结
  • 2.注解 @Async 介绍
    • 2.1.用在方法上
    • 2.2.用在类上
  • 3.使用演示
    • 3.1.在启动类或者配置类上增加 @EnableAsync 注解
    • 3.2.在异步方法上增加 @Async 注解
    • 3.3.调用异步方法
    • 3.4.测试
    • 3.5.其它说明
  • 4.注意事项
    • 4.1.@Async 注解失效的常见情况
    • 4.2.使用 @Async 注解可能会导致循环依赖
      • 4.2.1.示例
      • 4.2.2.原因分析
      • 4.2.3.解决方案

1.同步调用与异步调用

在 Java 中,同步调用和异步调用是两种不同的操作方式,用于处理方法调用和任务执行。

1.1.同步调用

(1)定义:同步调用指的是调用方法时,调用者会等待被调用的方法执行完成后才继续执行后续的代码。也就是说,方法调用是阻塞的,当前线程会被阻塞直到方法执行结束并返回结果。

(2)示例

public class SyncExample {
    public static void main(String[] args) {
        System.out.println("Start");

        //同步调用
        String result = longRunningTask();
        System.out.println("Result: " + result);

        System.out.println("End");
    }

    public static String longRunningTask() {
        try {
            //模拟长时间运行的任务
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Task Completed";
    }
}

(3)解释
在上述代码中,longRunningTask() 方法会暂停当前线程 3 秒钟,然后返回结果。在 main 方法中,longRunningTask() 的调用是同步的,System.out.println("Result: " + result); 会等待 longRunningTask() 完成后才执行。

1.2.异步调用

(1)定义:异步调用指的是调用方法时,调用者不会等待方法执行完成,而是立即继续执行后续的代码。被调用的方法在后台线程中执行,调用者可以通过回调、Future 模式或其他机制获取结果。

(2)示例

import java.util.concurrent.CompletableFuture;

public class AsyncExample {
    public static void main(String[] args) {
        System.out.println("Start");

        //异步调用
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> longRunningTask());

        //继续执行其他操作
        System.out.println("Doing other work while waiting for result...");

        //获取异步任务的结果(会阻塞,直到任务完成)
        future.thenAccept(result -> System.out.println("Result: " + result));

        System.out.println("End");
    }

    public static String longRunningTask() {
        try {
            //模拟长时间运行的任务
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Task Completed";
    }
}

(3)解释:在上述代码中,longRunningTask() 被异步执行,通过 CompletableFuture.supplyAsync 方法。在异步执行期间,System.out.println("Doing other work while waiting for result..."); 会继续执行,不会被阻塞。future.thenAccept(result -> System.out.println("Result: " + result)); 会在任务完成后处理结果。

1.3.总结

  • 同步调用
    • 特点:调用方在发起方法调用后,必须等待被调用的方法完成并返回结果之后,才能继续执行后续的操作(即会阻塞)。在同步调用中,调用方和被调用方的执行顺序是严格按照调用顺序进行的。
    • 适用场景:
      • 依赖顺序:当某个操作必须在另一个操作完成后才能进行时。
      • 简单任务:当任务执行时间短且没有并发问题时。
      • 确保执行顺序:需要确保多个步骤按顺序完成,比如数据库事务处理。
  • 异步调用
    • 特点:调用方发起方法调用后,不必等待被调用的方法完成,而是可以继续执行后续的操作。被调用的方法将在后台线程中运行,完成后会通知调用方结果或执行回调。
    • 适用场景:
      • 任务需要较长时间完成,阻塞主线程会影响程序的响应性。
      • 需要处理多个任务时,通过异步调用可以提高并发处理能力。
      • 用户界面应用中,异步操作可以防止界面冻结,提升用户体验。

2.注解 @Async 介绍

@Async 注解中的 Async 是单词 Asynchronous 的缩写。Asynchronous 指的是“异步的”,即操作或方法的执行不会阻塞当前线程,允许在后台执行任务的同时继续进行其他操作。使用 @Async 注解可以让 Spring 在后台线程池中异步执行被注解的方法,从而提升应用的性能和响应能力。@Async 注解的代码如下所示:

package org.springframework.scheduling.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
public @interface Async {
    String value() default "";
}

@Async 注解用在方法和类上的作用分别如下:

2.1.用在方法上

(1)功能:标记方法为异步执行,这意味着方法将在一个新的线程中执行,不会阻塞调用该方法的线程。并且方法所在的类必须要被 Spring 作为 Bean 管理。
(2)限制:只对 public 方法有效,且方法返回类型为 void 或者 Future
(3)例子:

@Async
public CompletableFuture<String> asyncMethod() {
    // 执行异步任务
}

2.2.用在类上

(1)功能:标记类中的所有 public 方法都为异步执行。这个类必须要被 Spring 作为 Bean 管理,并且所有被 @Async 注解的方法都会异步执行。
(2)限制:类上使用 @Async 不一定能保证类中所有方法都异步执行,特别是如果方法被类内部直接调用时,例如方法 asyncMethod2 在同一类内直接调用方法 asyncMethod1asyncMethod1在当前线程中同步执行,而不是异步执行
(3)例子:

@Async
@Service
public class MyService {
    public CompletableFuture<String> asyncMethod1() {
        // 执行异步任务
    }

    public CompletableFuture<String> asyncMethod2() {
        // 执行异步任务
    }
}

3.使用演示

一般来说,@Async 注解用在方法上来指定特定方法异步执行,标记整个类的方式较少使用。

3.1.在启动类或者配置类上增加 @EnableAsync 注解

例如,在 Spring Boot 项目的启动类上增加 @EnableAsync 注解:

@EnableAsync	//开启异步调用
@SpringBootApplication
public class MyExampleApplication {

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

}

@EnableAsync 注解用于启用 Spring 框架的异步方法执行功能。它使得应用程序能够处理被 @Async 注解标记的方法,确保这些方法在独立的线程中异步执行。将 @EnableAsync 注解添加到配置类或者启动类上后,Spring 将自动配置所需的支持,并允许 @Async 注解在应用程序中生效。

3.2.在异步方法上增加 @Async 注解

package com.example.myexample.service.async;

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

@Slf4j
@Service
public class MyAsyncTask {
    @Async
    public void notifyUser() {
        log.info("asyncTask 方法所处的线程:{}", Thread.currentThread().getName());
        //模拟处理异步任务
        try {
            Thread.sleep(3000);
            log.info("通知用户保存订单成功");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

3.3.调用异步方法

package com.example.myexample.controller;

import com.example.myexample.service.OrderService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("asyncTest")
public class AsyncTestController {
    @Resource
    private OrderService orderService;

    @PostMapping
    public String saveOrder() {
        orderService.saveOrder();
        return "success";
    }
}
package com.example.myexample.service;

public interface OrderService {
    void saveOrder();
}
package com.example.myexample.service.impl;

import com.example.myexample.service.OrderService;
import com.example.myexample.service.async.MyAsyncTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private MyAsyncTask myAsyncTask;

    @Override
    public void saveOrder() {
        log.info("saveOrder 方法所处的线程:{}", Thread.currentThread().getName());
        log.info("保存订单");
        //调用异步方法
        myAsyncTask.notifyUser();
        postProcess();
    }

	public void postProcess() {
        log.info("postProcess 方法所处的线程:{}", Thread.currentThread().getName());
    }
}

上述代码只包括了调用异步方法的核心部分,其它部分代码(例如 Controller 层)并未给出。

3.4.测试

使用 Postman 进行测试:

http://localhost:8080/asyncTest/

在这里插入图片描述

日志打印结果如下,从中可以发现:方法 saveOrdernotifyUser 在不同的线程中执行了,即 notifyUser 确实被异步调用了。

在这里插入图片描述

3.5.其它说明

如果要将异步方法改为有返回值,可以将返回类型从 void 改为 Future<T> CompletableFuture<T>。这里我们使用 CompletableFuture 作为返回类型:

@Async
public CompletableFuture<String> notifyUser2() {
    log.info("asyncTask 方法所处的线程:{}", Thread.currentThread().getName());
    // 模拟处理异步任务
    try {
        Thread.sleep(3000);
        log.info("通知用户保存订单成功");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    return CompletableFuture.completedFuture("通知用户保存订单成功");
}
public void saveOrder2() {
    log.info("saveOrder 方法所处的线程:{}", Thread.currentThread().getName());
    log.info("保存订单");
    CompletableFuture<String> future = myAsyncTask.notifyUser2();
    //在需要时可以调用 get() 来阻塞当前线程直到异步任务完成
    try {
        String result = future.get(); // 这将阻塞直到异步任务完成
        System.out.println("任务结果: " + result);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

有关 Future 模式的相关知识可以参考 Java 并发编程面试题——Future 模式这篇文章。

4.注意事项

4.1.@Async 注解失效的常见情况

@Async 注解在某些情况下可能会失效,包括:

  • 方法定义不符合要求@Async 方法必须是 public,并且不能是 finalstaticprivate
  • 类未被 Spring 扫描:确保含有 @Async 注解的类被 Spring 扫描到。通常需要将类放在 @Component@Service 或其他 Spring 管理的组件中。
  • 缺少 @EnableAsync 注解:在配置类上未添加 @EnableAsync 注解,Spring 不会启用异步处理。
  • 方法调用在同一类内部@Async 方法被同一类的其他方法调用时不会生效,因为 Spring 的代理机制无法处理同一类内的调用。
  • 返回值处理不当:异步方法通常返回 Futurevoid,确保返回值类型正确处理。

4.2.使用 @Async 注解可能会导致循环依赖

4.2.1.示例

需要注意的是,如果代码编写不当,在使用 @Async 注解可能会导致循环依赖,例如:

@Service
public class CDTestsService1Impl implements CDTestsService1 {
    @Autowired
    private CDTestsService2 cdTestsService2;

    @Async
    @Override
    public void performAsyncTask() {
        System.out.println("performAsyncTask...");
    }
}
@Service
public class CDTestsService2Impl implements CDTestsService2 {
    @Autowired
    private CDTestsService1 cdTestsService1;

    @Override
    public void performTask() {
        cdTestsService1.performAsyncTask();
    }
}
@RestController
@RequestMapping("asyncTest")
public class AsyncTestController {

    @Resource
    private CDTestsService2 cdTestsService2;

    @PostMapping("/cdTest")
    public String cdTest() {
        cdTestsService2.performTask();
        return "success";
    }
}

当启动项目时,会出现循环依赖的异常提示:
在这里插入图片描述

4.2.2.原因分析

  • 循环依赖:
    • CDTestsService1Impl 依赖于 CDTestsService2Impl,并且 CDTestsService2Impl 依赖于 CDTestsService1Impl。这种相互依赖会导致 Spring 容器在创建这些 bean 时发生问题。
    • 当 Spring 容器创建 CDTestsService1Impl 时,它需要注入 CDTestsService2Impl。接着,创建 CDTestsService2Impl 时,它需要注入 CDTestsService1Impl。这样,就会形成一个死循环,因为两个 bean 互相等待对方被完全创建和注入。
  • @Async 注解的影响:
    • @Async 注解用于异步执行方法。这意味着 performAsyncTask 方法会在不同的线程中运行,因此不会阻塞当前线程。但是,Spring 在执行异步方法时,仍然需要确保异步方法的 bean 实例在调用之前已完全创建和注入。
    • 由于 performAsyncTask 是异步的,Spring 在调用这个方法时可能会遇到由于循环依赖未完全解决而引发的异常。

4.2.3.解决方案

(1)使用构造函数注入
使用构造函数注入可以避免循环依赖问题。构造函数注入在 bean 创建时确保所有依赖项都已解决,因此更可靠。

@Service
public class CDTestsService1Impl implements CDTestsService1 {
    private final CDTestsService2 cdTestsService2;

    @Autowired
    public CDTestsService1Impl(CDTestsService2 cdTestsService2) {
        this.cdTestsService2 = cdTestsService2;
    }

    @Async
    @Override
    public void performAsyncTask() {
        System.out.println("performAsyncTask...");
    }
}

@Service
public class CDTestsService2Impl implements CDTestsService2 {
    private final CDTestsService1 cdTestsService1;

    @Autowired
    public CDTestsService2Impl(CDTestsService1 cdTestsService1) {
        this.cdTestsService1 = cdTestsService1;
    }

    @Override
    public void performTask() {
        cdTestsService1.performAsyncTask();
    }
}

(2)使用 @Lazy 注解
@Lazy 注解可以推迟 bean 的初始化,解决循环依赖问题。你可以将 @Lazy 注解添加到字段注入中,以便在需要时才加载依赖。

@Service
public class CDTestsService1Impl implements CDTestsService1 {
    @Autowired
    @Lazy
    private CDTestsService2 cdTestsService2;

    @Async
    @Override
    public void performAsyncTask() {
        System.out.println("performAsyncTask...");
    }
}

@Service
public class CDTestsService2Impl implements CDTestsService2 {
    @Autowired
    @Lazy
    private CDTestsService1 cdTestsService1;

    @Override
    public void performTask() {
        cdTestsService1.performAsyncTask();
    }
}

(3)分离异步调用
如果 performAsyncTask 不需要立即执行,考虑将它移到另一个服务中,或通过其他机制避免直接的循环依赖。

@Service
public class CDTestsService1Impl implements CDTestsService1 {
    @Autowired
    private TaskExecutor taskExecutor;  // Spring 提供的 TaskExecutor

    @Override
    public void performAsyncTask() {
        taskExecutor.execute(() -> {
            System.out.println("performAsyncTask...");
        });
    }
}

@Service
public class CDTestsService2Impl implements CDTestsService2 {
    @Autowired
    private CDTestsService1 cdTestsService1;

    @Override
    public void performTask() {
        cdTestsService1.performAsyncTask();
    }
}

使用 TaskExecutor 来执行异步任务可以将异步执行的逻辑与服务依赖解耦,避免直接在服务之间形成循环依赖。

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

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

相关文章

【Qt绘图】—— 运用Qt进行绘图

目录 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;绘制各种形状 2.1 绘制线段 2.2 绘制矩形 2.3 绘制圆形 2.4 绘制文本 2.5 设置画笔 2.6 设置画刷 &#xff08;三&#xff09;绘制图片 3.1 绘制简单图片 3.2 平移图片 3.3 缩放图片 3.4…

Linux 手动安装Ollama

Linux 离线安装Ollama 前言 不知道为什么 在阿里云服务器上 执行curl -fsSL https://ollama.com/install.sh | sh一键安装 非常慢 所以只能手动装了 1.到 https://ollama.com/install.sh 下载安装执行文件 修改其中 下载和安装部分代码 if curl -I --silent --fail --location…

Python数据分析-Numpy快速入门

一、什么是Numpy 二、 创建 Numpy ndarray对象 三、数组中的维度 1.各种维度数组 2.检查维度数 3.创建更高维度的数组 四、数组索引 1.访问数组元素 2.访问2-D数组元素 其他维度的同理 3.负索引 五、数据裁剪&#xff1a;要头不要尾 1.裁剪数组 demo&#xff1a; 2.负裁…

构建基于 Feign 的微服务:从 Eureka 到负载均衡的实践 --day05

目录 步骤1&#xff1a;创建父工程feign-1步骤2&#xff1a;改造服务提供者使用 RequestMapping使用 GetMapping 步骤3&#xff1a;改造服务消费者为Feign客户端&#xff08;1&#xff09;添加Feign依赖&#xff08;2&#xff09;添加EnableFeignClients注解&#xff08;3&…

YoloV10 训练自己的数据集(推理,转化,C#部署)

目录 一、下载 三、开始训练 train.py detect.py export.py 超参数都在这个路径下 四、C#读取yolov10模型进行部署推理 如下程序是用来配置openvino 配置好引用后就可以生成dll了 再创建一个控件&#xff0c;作为显示 net framework 4.8版本的 再nuget工具箱里下载 …

thinkphp6开发的通用网站系统源码

thinkphp6开发的通用网站系统源码。 基于ThinkPHP6框架开发的通用后台权限管理系统&#xff0c;底层采用国内最流行的ThinkPHP6框架&#xff0c; 支持内容管理、文章管理、用户管理、权限管理、角色管理等功能。 代码下载

Cookie和Session的对比

Cookie和Sesion 一、cookie和session创建对象 2024/9/15 10:23:59 你想了解的是如何在某种编程语言中创建和管理 cookies 和 sessions 吗&#xff1f;如果是的话&#xff0c;具体是哪个语言或框架呢&#xff1f; 2024/9/15 10:24:09 二、创建对象是客户端还是服务器 2024/…

20Kg载重30分钟续航多旋翼无人机技术详解

一、机架与结构设计 1. 材料选择&#xff1a;为了确保无人机能够承载20Kg的负载&#xff0c;同时实现30分钟的续航&#xff0c;其机架材料需选用轻质高强度的材料&#xff0c;如碳纤维或铝合金。这些材料不仅具有良好的承重能力&#xff0c;还能有效减轻无人机的整体重量&…

一步一步搭建AI智能体应用

您可以在百炼控制台以零代码的方式快速创建智能体应用&#xff0c;并将RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;以及插件能力集成进来。应用创建完成后&#xff0c;您可以通过控制台或API的方式来使用。 以下均以 大模型应用指代 智…

微信小程序使用 ==== 粘性布局

目录 Chrome杀了个回马枪 position:sticky简介 你可能不知道的position:sticky 深入理解粘性定位的计算规则 粘性定位其他特征 代码实现 微信小程序在scroll-view中使用sticky Chrome杀了个回马枪 position:sticky早有耳闻也有所了解&#xff0c;后来&#xff0c;Chro…

通过API接口获取下来的数据需要怎样应用?

在当今数字化时代&#xff0c;通过API接口获取数据已成为企业获取、处理和分析信息的重要手段。API接口不仅能够提高数据交互的效率&#xff0c;还能促进数据的安全性和灵活性。以下是如何将通过API接口获取的数据有效应用的一些方法和策略。 数据整合与分析 企业可以通过API接…

QPS和TPS的区别简单理解

QPS&#xff08;Queries Per Second&#xff09; QPS是指每秒查询率&#xff0c;它是衡量服务器处理能力的一个指标&#xff0c;表示服务器在一秒钟内能够响应的查询次数。这个指标通常用于数据库或服务器的性能测试&#xff0c;反映了服务器在规定时间内处理流量的能力。QPS …

浮动元素详解

浮动元素 代码实现&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>浮动元素</title><style>#container1 {width: 400px;height: 50px;background-color: lightgrey;border: 1px solid;}#contai…

世界杯足球赛网站---附源码73185

摘 要 随着互联网的普及和足球运动的全球性影响力&#xff0c;建立一个专门的世界杯足球赛网站成为了与球迷互动和传播赛事信息的重要途径。本论文聚焦于世界杯足球赛网站的设计与实现&#xff0c;旨在探讨如何利用现代技术为球迷提供一个全方位的足球赛事体验。 通过对 Spring…

上传头像,访问本地图片

文件大坑&#xff1a; web项目&#xff1a;首先不能直接访问本地资源&#xff0c;只能够访问服务器上的资源。 所以我想就储存数据到服务器&#xff0c;但是这样有个问题就是&#xff0c;当重新启动程序时&#xff0c;服务器上的所有文件会被重新编译&#xff0c;导致之前的文…

HarmonyOS开发实战( Beta5.0)自动生成动态路由实践

鸿蒙HarmonyOS开发往期必看&#xff1a; HarmonyOS NEXT应用开发性能实践总结 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门到精通&#xff09; 介绍 本示例将介绍如何使用装饰器和插件&#xff0c;自动生成动…

【UE5】使用2DFlipbook图作为体积纹理,实现实时绘制体积纹理

这是一篇对“Creating a Volumetric Ray Marcher-Shader Bits”的学习心得 文章时间很早&#xff0c;因此这里针对UE5对原文做出兼容性修正&#xff08;为避免累赘不做出注明。链接如上&#xff0c;有需要自行学习&#xff09; 以及最后对Custom做可能的蓝图移植&#xff0c;做…

Qt中样式表常用的属性名称定义

Qt中&#xff0c;用好样式表&#xff0c;不但可以做出意想不到的酷炫效果&#xff0c;有时候也能减轻开发量&#xff0c;可能由于你不了解某些样式使用&#xff0c;想破脑袋通过代码实现的效果&#xff0c;反倒不如别人用样式&#xff0c;一两句样式脚本就搞定。 Qt中&#xff…

第T8周:猫狗识别

本文为365天深度学习训练营 中的学习记录博客原作者&#xff1a;K同学啊 ●难度&#xff1a;夯实基础⭐⭐ ●语言&#xff1a;Python3、TensorFlow2 要求&#xff1a; 1.了解model.train_on_batch()并运用 2.了解tqdm&#xff0c;并使用tqdm实现可视化进度条 拔高&#xff08…

Day11-K8S日志收集及搭建高可用的kubernetes集群实战案例

Day11-K8S日志收集及搭建高可用的kubernetes集群实战案例 0、昨日内容回顾1、日志收集1.1 K8S日志收集项目之架构图解三种方案1.2 部署ES1.3 部署kibana1.4 部署filebeat 2、监控系统2.1 部署prometheus 3、K8S二进制部署3.1 K8S二进制部署准备环境3.2 基础组件安装3.3 生成K8S…