java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring  @Async 注解

Spring 框架@Async 注解详解

作者:java全套学习资料

这篇文章主要介绍了Spring框架@Async注解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

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 注解在某些情况下可能会失效,包括:

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.原因分析

循环依赖:

@Async 注解的影响:

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 来执行异步任务可以将异步执行的逻辑与服务依赖解耦,避免直接在服务之间形成循环依赖。

到此这篇关于Spring 框架@Async 注解的文章就介绍到这了,更多相关Spring  @Async 注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文