一、异步调用
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 | 同步的执行任务,任务的执行是在主线程中,不会启动新的线程来执行提交的任务。主要使用在没有必要使用多线程的情况,如较为简单的测试用例。 |
ConcurrentTaskExecutor | Executor的适配类,不推荐使用。如果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);
}
}