SpringBoot【异步调用】

博客介绍了Spring中的异步调用,包括在启动类加@EnableAsync使@Async注解生效,介绍了官方实现的7个TaskExecuter及线程池配置方法。还说明了创建异步方法类的注解使用和调用演示,给出异步发送邮件填坑链接,最后指出@Async失效的原因及解决办法。

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

一、异步调用

1.启动类
需要在启动类加入@EnableAsync,使异步调用@Async注解生效

package com.stone.project;

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

@SpringBootApplication
@EnableAsync
@EnableScheduling
public class App 
{
    public static void main( String[] args )
    {
    	SpringApplication.run(App.class, args);
    }
}

2.Service
在方法上添加@Async注解,本质同提取异步方法到一个单独的类中相同。

package com.stone.project.service;
import java.util.stream.IntStream;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

	@Service
	public class UserService {
		@Async
	    public void sendSms(){
			long start = System.currentTimeMillis();
	        System.out.println("####sendSms####   1");
	        IntStream.range(0, 5).forEach(d -> {
	            try {
	                Thread.sleep(1000);
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	        });
	        long end = System.currentTimeMillis();
	        System.out.println("elapsed " + (end-start) + " 毫秒");
	        System.out.println("####sendSms####   2");
	}
}

二、异步线程池

以下是官方已经实现的全部7个TaskExecuter。Spring宣称对于任何场景,这些TaskExecuter完全够用了:

名字特点
SimpleAsyncTaskExecutor线程不会重用,每次调用时都会重新启动一个新的线程;但它有一个最大同时执行的线程数的限制;
SyncTaskExecutor同步的执行任务,任务的执行是在主线程中,不会启动新的线程来执行提交的任务。主要使用在没有必要使用多线程的情况,如较为简单的测试用例。
ConcurrentTaskExecutorExecutor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。
SimpleThreadPoolTaskExecutor它是Quartz中SimpleThreadPool的一个实现,用于监听Spring生命周期回调事件。它主要使用在需要一个线程池来被Quartz和非Quartz中的对象同时共享使用的情况。
ThreadPoolTaskExecutor最常用,要求jdk版本大于等于5。可以在程序而不是xml里修改线程池的配置。其实质是对java.util.concurrent.ThreadPoolExecutor的包装。
TimerTaskExecutor用户提交的所有任务都将交由Timer本身的单个线程执行,这一单个线程会以串行的方式完成用户提交给它的所有任务。
WorkManagerTaskExecutor实现了CommonJ中的WorkManager接口,是在Spring中使用CommonJ的WorkManager时的核心类。

1.线程池配置方法一

package com.stone.project.config;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
	private static final int corePoolSize = 10;       		// 核心线程数(默认线程数)
	private static final int maxPoolSize = 100;			    // 最大线程数
	private static final int keepAliveTime = 10;			// 允许线程空闲时间(单位:默认为秒)
	private static final int queueCapacity = 200;			// 缓冲队列数
	private static final String threadNamePrefix = "Async-Service-"; // 线程池名前缀	
	@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
	public ThreadPoolTaskExecutor taskExecutor(){
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		// 线程池维护线程的最少数量
		executor.setCorePoolSize(corePoolSize);
		// 线程池维护线程的最大数量 
		executor.setMaxPoolSize(maxPoolSize);
		// 线程池维护线程的缓冲队列数
		executor.setQueueCapacity(queueCapacity);
		// 线程池允许线程空闲时间
		executor.setKeepAliveSeconds(keepAliveTime);
		// 线程池名前缀
		executor.setThreadNamePrefix(threadNamePrefix);		
		// 线程池对拒绝任务的处理策略
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		// 初始化
		executor.initialize();
		
		return executor;
	}
}

2. 测试方法一

  • 在需要异步执行的方法上加入此注解即可@Async(“threadPool”),threadPool为自定义线程池
package com.stone.project.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncThreadPoolService {
	Logger log = LoggerFactory.getLogger(AsyncThreadPoolService.class);
	
	// 发送提醒短信 1
	@Async("taskExecutor")
	public void sendMessage1() throws InterruptedException {
		log.info("发送短信方法---- 1   执行开始" + Thread.currentThread().getName());
		Thread.sleep(5000); // 模拟耗时
		log.info("发送短信方法---- 1   执行结束");
	}
	
	// 发送提醒短信 2
	@Async("taskExecutor")
	public void sendMessage2() throws InterruptedException {		
		log.info("发送短信方法---- 2   执行开始" + Thread.currentThread().getName());
		Thread.sleep(2000); // 模拟耗时
		log.info("发送短信方法---- 2   执行结束");
	}	
}

三、创建异步方法类

1.类和方法上使用注解

  • 在类上使用 @Component 注解,在方法上使用 @Async 注解。

以下例子仅用于演示功能:

package com.stone.project.task;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

@Component
public class AsyncTask {
	@Async
	public void task1() throws InterruptedException {
		long start = System.currentTimeMillis();
		Thread.sleep(1000);
		long end = System.currentTimeMillis();
		System.out.println("task1任务耗时:" + (end - start) + "ms");
	}

	@Async
	public void task2() throws InterruptedException {
		long start = System.currentTimeMillis();
		Thread.sleep(2000);
		long end = System.currentTimeMillis();
		System.out.println("task2任务耗时:" + (end - start) + "ms");
	}

	@Async
	public void task3() throws InterruptedException {
		long start = System.currentTimeMillis();
		Thread.sleep(3000);
		long end = System.currentTimeMillis();
		System.out.println("task3任务耗时:" + (end - start) + "ms");
	}
}

2.调用演示
下面在 Controller 里进行调用演示

public class AsyncTaskController {
	@Autowired  
    private AsyncTask asyncTask;     

	@RequestMapping("/asyncTask")
	public String doTask() throws InterruptedException {
		long start = System.currentTimeMillis();
	
		asyncTask.task1();
		asyncTask.task2();
		asyncTask.task3();
		
		long end= System.currentTimeMillis();
		String result = "task任务总耗时:" + (end- start) + "ms";
		System.out.println(result);
		return result;
	}	
}

四、异步发送邮件填坑

https://www.jianshu.com/p/3fd3dcece815
//编译不过

五、注意事项

1、@Async失效
(1)异步方法使用static修饰
(2)异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类
(3)异步方法不能与异步方法在同一个类中
(4)类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象
(5)如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解
(6)在Async 方法上标注@Transactional是没用的。 在Async 方法调用的方法上标注@Transactional 有效。
2、解决方法
同一个类中异步法调用方式
原因
调用的异步方法,不能为同一个类的方法(包括同一个类的内部类),简单来说,因为Spring在启动扫描时会为其创建一个代理类,而同类调用时,还是调用本身的代理类的,所以和平常调用是一样的。其他的注解如@Cache等也是一样的道理,说白了,就是Spring的代理机制造成的。所以在开发中,最好把异步服务单独抽出一个类来管理。
解决办法
其实我们的注入对象都是从Spring容器中给当前Spring组件进行成员变量的赋值,由于某些类使用了AOP注解,那么实际上在Spring容器中实际存在的是它的代理对象。

注:经验证,下面两种方法都有效,但是千万不要忘记在启动类上添加@EnableAsync注解。

方法1:可以通过上下文获取自己的代理对象调用异步方法。
方法2:将异步任务单独放到一个类。

https://www.cnblogs.com/shamo89/p/9095380.html
方法3:通过SpringUtil工具类获取本类的代理类,再通过代理类调用异步类的方法。
上述三种方案,示例如下:

package com.stone.project.controller;

import java.util.List;
import java.util.concurrent.Future;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.stone.project.service.AsyncThreadPoolService;
import com.stone.project.service.UserService;
import com.stone.project.task.AsyncTask;
import com.stone.project.util.SpringUtil;

@RestController  
@RequestMapping("/test")
public class AsyncTaskController {
	@Autowired
	ApplicationContext applicationContext;
	
	@Autowired  
    private AsyncTask asyncTask;     
	
	@Autowired
	private UserService userService;
	
	@Autowired
	private AsyncThreadPoolService threadPoolService;
/***
 * 
 *   此方法测试内容有:
 * 1)调用同一个类中的异步方法(不能达到异步效果);
 * 2)通过ApplicationContext获取本类的代理类,再通过代理类调用异步类的方法。(有效)
 * 3)通过SpringUtil工具类获取本类的代理类,再通过代理类调用异步类的方法。(有效)
 * 4)把异步方法提取到一个单独的类AsyncTask里,然后通过它调用异步方法。(有效)
 * 
 *    注:所有的验证都依赖于启动类App里添加注解@EnableAsync   
 * 
 * */
	@RequestMapping("/asyncTask")
	public String doTask() throws InterruptedException {
//		AsyncTaskController proxy1 = (AsyncTaskController) applicationContext.getBean(AsyncTaskController.class);
		 AsyncTaskController proxy2 = SpringUtil.getBean(AsyncTaskController.class);
		long currentTimeMillis = System.currentTimeMillis();
		
		//直接调用AsyncTaskController的三个异步函数,将不会有异步执行的效果。
//	        this.task1();  
//	        this.task2();  
//	        this.task3();  
		
		//此方法有效
//		proxy1.task1();
//		proxy1.task2();
//		proxy1.task3();
		
		//此方法有效
		proxy2.task1();
		proxy2.task2();
		proxy2.task3();		
		
		//此方法有效
//		asyncTask.task1();
//		asyncTask.task2();
//		asyncTask.task3();
		
		long currentTimeMillis1 = System.currentTimeMillis();
		String result = "task任务总耗时:" + (currentTimeMillis1 - currentTimeMillis) + "ms";
		System.out.println(result);
		return result;
	}	
	
	/****
	 * 功能:验证异步方法的返回值,以及如何控制流程
	 * */
	@RequestMapping("/testAsyncTask")
	public String testAsyncTask() throws InterruptedException, Exception {
		long start = System.currentTimeMillis();		
		Future<Boolean> a = asyncTask.doTask11();
		Future<Boolean> b = asyncTask.doTask22();
		Future<Boolean> c = asyncTask.doTask33();
		Future<List<String>> d = asyncTask.doTask4();
		//三个异步方法都执行结束后再往下执行
		while (!a.isDone() || !b.isDone() || !c.isDone() || !d.isDone()) {
			if(a.isDone() && b.isDone() && c.isDone() && d.isDone()) {
				break;
			}
		}			
		
		Boolean a1 = a.get();
		Boolean b1 = b.get();
		Boolean c1 = c.get();
		List<String> list = d.get();
		long end = System.currentTimeMillis();
		String result = "任务全部完成总耗时: " + (end-start) + "毫秒";
		System.out.println(result);
		return result;
	}
	
	/**
	 * 功能:验证服务中的异步方法
	 * */
	@RequestMapping("/sendSms")
	public String sendSms() {
		System.out.println("####sendSms begin in controller####");
		userService.sendSms();
		System.out.println("####sendSms end in controller####");
		return "sendSms response finished";
	}
	
	@RequestMapping("/testAsyncThreadPool")
	public String testAsyncThreadPoolService() throws InterruptedException{
		System.out.println("####testAsyncThreadPool start in controller####");
		threadPoolService.sendMessage1();
		threadPoolService.sendMessage2();
		System.out.println("####testAsyncThreadPool end in controller####");
		return "ok";
	}
	
	/**功能:同类中的异步方法1,供验证所用
	 * */
	@Async
	public void task1() throws InterruptedException {
		long currentTimeMillis = System.currentTimeMillis();
		Thread.sleep(1000);
		long currentTimeMillis1 = System.currentTimeMillis();
		System.out.println("task1任务耗时:" + (currentTimeMillis1 - currentTimeMillis) + "ms");
	}

	/**功能:同类中的异步方法2,供验证所用
	 * */
	@Async
	public void task2() throws InterruptedException {
		long currentTimeMillis = System.currentTimeMillis();
		Thread.sleep(2000);
		long currentTimeMillis1 = System.currentTimeMillis();
		System.out.println("task2任务耗时:" + (currentTimeMillis1 - currentTimeMillis) + "ms");
	}

	/**功能:同类中的异步方法3,供验证所用
	 * */
	@Async
	public void task3() throws InterruptedException {
		long currentTimeMillis = System.currentTimeMillis();
		Thread.sleep(3000);
		long currentTimeMillis1 = System.currentTimeMillis();
		System.out.println("task3任务耗时:" + (currentTimeMillis1 - currentTimeMillis) + "ms");
	}
}


附SpringUtil工具类:

package com.stone.project.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtil implements ApplicationContextAware{
	private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if(SpringUtil.applicationContext == null) {
            SpringUtil.applicationContext = applicationContext;
        }
        
        System.out.println("========ApplicationContext配置成功,在普通类可以通过调用SpringUtils.getAppContext()获取applicationContext对象,applicationContext="+SpringUtil.applicationContext+"========");
    }

    //获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    //通过name获取 Bean.
    public static Object getBean(String name){
        return getApplicationContext().getBean(name);
    }

    //通过class获取Bean.
    public static <T> T getBean(Class<T> clazz){
        return getApplicationContext().getBean(clazz);
    }

    //通过name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name,Class<T> clazz){
        return getApplicationContext().getBean(name, clazz);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值