异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等待上一步程序执行完即可执行。
实现异步任务的方式有很多,但是可以总结为多线程异步和多进程异步。
多线程异步:
多进程异步:
本篇重点讲述多线程异步任务的执行方式。多进程方式可以查看《SpringBoot入门建站全系列(十七)整合ActiveMq(JMS类消息队列)》和《SpringBoot入门建站全系列(十八)整合RabbitMQ(AMQP类消息队列)》中的消息发送方式。
多线程的异步任务执行有几种方式,还是前面所说的那样,方式虽然不同,但原理是一样的,就是在新线程中执行任务,但是还是要说一下这几种方式。
本文是在Springboot环境中测试的。引入spring-boot-starter-web即可。如果不会搭建,可以打开这篇文章看一看《SpringBoot入门建站全系列(一)项目建立》。
在web项目中测试会比较方便,我们利用RequestContextHolder获取ThreadLocal的Request对象来判断线程是否改变了
假设我们要执行的任务是这样的, 为了异步执行AsyncTaskService 中的asyncTask方法:
package com.cff.springbootwork.async.service;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Service
public class AsyncTaskService {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 普通的一方法而已,供异步任务调用
*/
public void asyncTask() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest curRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
log.info("异步任务开始执行,当前请求属性test为:{}", curRequest.getAttribute("test"));
} else {
log.info("异步任务不是同一个线程了,别想拿ThreadLocal对象了");
}
try {
Thread.sleep(5000);
log.info("我是异步任务,我就是个打印!");
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("5s后异步任务终于执行完成");
}
}
首先我们在Request中塞入一个值,后面用来获取判断是否是主线程。
然后用new Thread新建线程执行异步任务。
我们将逻辑写在AsyncService中,来调用AsyncTaskService的asyncTask方法。
AsyncService:
/**
* 测试new Thread 异步任务
*/
public void asyncThread() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
Thread thread1 = new Thread(new Runnable() {
public void run() {
asyncTaskService.asyncTask();
}
});
thread1.start();
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
结果如下:
2019-08-08 16:39:50,516 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:39:50,517 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:39:50,522 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:39:50,530 [Thread-4][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:39:55,530 [Thread-4][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:39:55,530 [Thread-4][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
首先我们在Request中塞入一个值,后面用来获取判断是否是主线程。
然后将异步任务扔给线程池执行。
这里我们分别使用Runnable和Callable来测试下。
先建立一个线程池:
package com.cff.springbootwork.async.service;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Service;
@Service
public class ThreadPoolService {
private ExecutorService executor;
@PostConstruct
public void init() {
// executor = new ThreadPoolExecutor(20, 30, 60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(),
// new ThreadPoolExecutor.AbortPolicy());
executor = Executors.newCachedThreadPool();
}
public void execute(Runnable task) {
executor.execute(task);
}
public <T> Future<T> submit(Callable<T> task) {
return executor.submit(task);
}
@PreDestroy
public void shutdown() {
executor.shutdown();
}
}
我们将逻辑写在AsyncService中,来调用AsyncTaskService的asyncTask方法。
AsyncService:
/**
* 测试 线程池 异步任务
*/
public void asyncThreadPool() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
threadPoolService.execute(new Runnable() {
public void run() {
asyncTaskService.asyncTask();
}
});
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
结果如下:
2019-08-08 16:44:19,136 [http-nio-8080-exec-4][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:44:19,136 [http-nio-8080-exec-4][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:44:19,137 [http-nio-8080-exec-4][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:44:19,138 [pool-1-thread-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:44:24,140 [pool-1-thread-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:44:24,140 [pool-1-thread-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
和new Thread方式一样。唯一区别就是它是线程池,线程可以回收,线程使用频繁的时候,减少线程创建回收的开销。
我们将逻辑写在AsyncService中,来调用AsyncTaskService的asyncTask方法。
AsyncService:
/**
* 测试 线程池 异步任务 Future回调
*/
public void asyncFuturePool() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
asyncTaskService.asyncTask();
return "1111";
}
};
Future<String> future = threadPoolService.submit(callable);
try {
// future.get(); // 阻塞函数,如果直接用,它就一直阻塞,就不是异步了。
if (future.isDone()) {
log.info("这么快就完成了?不可能!");
String result = future.get();
log.info("任务结果为:{}", result);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e1) {
e1.printStackTrace();
}
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
如果注掉future.get(),结果如下:
2019-08-08 16:46:44,001 [http-nio-8080-exec-7][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:46:44,002 [http-nio-8080-exec-7][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:46:44,003 [http-nio-8080-exec-7][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:46:44,003 [pool-1-thread-2][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:46:49,003 [pool-1-thread-2][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:46:49,003 [pool-1-thread-2][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
这个过程和4.1完全一样了。
如果不注掉future.get(),结果如下:
2019-08-08 16:48:02,472 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:48:02,472 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:48:02,478 [pool-1-thread-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:48:07,478 [pool-1-thread-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:48:07,478 [pool-1-thread-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
2019-08-08 16:48:07,478 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 这么快就完成了?不可能!
2019-08-08 16:48:07,478 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务结果为:1111
2019-08-08 16:48:07,478 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
虽然拿到了结果,也使用了多线程,但是这个过程变成同步了,因为主线程一直等待另外一个线程执行完才执行下一步。
@Async注解的方法,不能和调用方法在同一个类中,因为它是动态代理调用的。同一个类中动态代理个毛啊。。
首先要使用注解@EnableAsync开启异步执行功能。
package com.cff.springbootwork.async.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AsyncConfig {
}
我们将逻辑写在AsyncService中,新建个方法asyncTaskAnnotation,加上@Async注解并调用AsyncTaskService的asyncTask方法,进行测试。
AsyncService:
/**
* 测试 @Async 对异步任务的支持
*/
@Async
public void asyncTaskAnnotation() {
asyncTaskService.asyncTask();
}
/**
* 测试 Spring的@Async 对异步任务的支持, 同一个类内的方法不能实现异步
*/
public void asyncSpringAnnotationOne() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
asyncTaskAnnotation();
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
测试结果如下:
2019-08-08 16:54:09,957 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:54:09,957 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:54:09,962 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 异步任务开始执行,当前请求属性test为:asdasd
2019-08-08 16:54:14,962 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:54:14,962 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
2019-08-08 16:54:14,962 [http-nio-8080-exec-1][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
可以看出,异步任务竟然可以拿到request的test属性了,表明这压根就是同一个线程,@Async无效。
我们将逻辑写在AsyncService中,在AsyncTaskService新建个方法asyncTaskAnnotation,加上@Async注解并调用asyncTask方法,进行测试。
AsyncService:
/**
* 测试 Spring的@Async 对异步任务的支持, 不同类的方法可以实现异步
*/
public void asyncSpringAnnotationMuti() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
asyncTaskService.asyncTaskAnnotation();
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
测试结果如下:
2019-08-08 16:56:50,964 [http-nio-8080-exec-6][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 开始执行任务
2019-08-08 16:56:50,964 [http-nio-8080-exec-6][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 请求属性test为:asdasd
2019-08-08 16:56:50,964 [http-nio-8080-exec-6][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncService] 任务提前执行完成,开始返回,请求属性test为:asdasd
2019-08-08 16:56:50,965 [SimpleAsyncTaskExecutor-2][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 异步任务不是同一个线程了,别想拿ThreadLocal对象了
2019-08-08 16:56:55,965 [SimpleAsyncTaskExecutor-2][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 我是异步任务,我就是个打印!
2019-08-08 16:56:55,965 [SimpleAsyncTaskExecutor-2][IP:|USER:][INFO com.cff.springbootwork.async.service.AsyncTaskService] 5s后异步任务终于执行完成
可以看出,异步任务拿不到request对象了,@Async正常使用。
AsyncRest:
package com.cff.springbootwork.async.web;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.cff.springbootwork.async.service.AsyncService;
/**
* 测试乐异步任务
*
* @author fufei
*
*/
@RestController
@RequestMapping("/async")
public class AsyncRest {
@Autowired
AsyncService asyncService;
@RequestMapping(value = "/thread", method = { RequestMethod.GET })
public String thread(HttpServletRequest request) {
request.setAttribute("test", "asdasd");
asyncService.asyncThread();
return "0000";
}
@RequestMapping(value = "/pool", method = { RequestMethod.GET })
public String pool(HttpServletRequest request) {
request.setAttribute("test", "asdasd");
asyncService.asyncThreadPool();
return "0000";
}
@RequestMapping(value = "/future", method = { RequestMethod.GET })
public String future(HttpServletRequest request) {
request.setAttribute("test", "asdasd");
asyncService.asyncFuturePool();
return "0000";
}
@RequestMapping(value = "/springOne", method = { RequestMethod.GET })
public String springOne(HttpServletRequest request) {
request.setAttribute("test", "asdasd");
asyncService.asyncSpringAnnotationOne();
return "0000";
}
@RequestMapping(value = "/springMuti", method = { RequestMethod.GET })
public String springMuti(HttpServletRequest request) {
request.setAttribute("test", "asdasd");
asyncService.asyncSpringAnnotationMuti();
return "0000";
}
}
AsyncService:
package com.cff.springbootwork.async.service;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Service
public class AsyncService {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
AsyncTaskService asyncTaskService;
@Autowired
ThreadPoolService threadPoolService;
/**
* 测试new Thread 异步任务
*/
public void asyncThread() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
Thread thread1 = new Thread(new Runnable() {
public void run() {
asyncTaskService.asyncTask();
}
});
thread1.start();
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
/**
* 测试 线程池 异步任务
*/
public void asyncThreadPool() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
threadPoolService.execute(new Runnable() {
public void run() {
asyncTaskService.asyncTask();
}
});
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
/**
* 测试 线程池 异步任务 Future回调
*/
public void asyncFuturePool() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
asyncTaskService.asyncTask();
return "1111";
}
};
Future<String> future = threadPoolService.submit(callable);
try {
// future.get(); // 阻塞函数,如果直接用,它就一直阻塞,就不是异步了。
if (future.isDone()) {
log.info("这么快就完成了?不可能!");
String result = future.get();
log.info("任务结果为:{}", result);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e1) {
e1.printStackTrace();
}
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
/**
* 测试 @Async 对异步任务的支持
*/
@Async
public void asyncTaskAnnotation() {
asyncTaskService.asyncTask();
}
/**
* 测试 Spring的@Async 对异步任务的支持, 同一个类内的方法不能实现异步
*/
public void asyncSpringAnnotationOne() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
asyncTaskAnnotation();
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
/**
* 测试 Spring的@Async 对异步任务的支持, 不同类的方法可以实现异步
*/
public void asyncSpringAnnotationMuti() {
log.info("开始执行任务");
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("请求属性test为:{}", request.getAttribute("test"));
asyncTaskService.asyncTaskAnnotation();
HttpServletRequest afterRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("任务提前执行完成,开始返回,请求属性test为:{}", afterRequest.getAttribute("test"));
}
}
AsyncTaskService:
package com.cff.springbootwork.async.service;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Service
public class AsyncTaskService {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 普通的一方法而已,供异步任务调用
*/
public void asyncTask() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest curRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
log.info("异步任务开始执行,当前请求属性test为:{}", curRequest.getAttribute("test"));
} else {
log.info("异步任务不是同一个线程了,别想拿ThreadLocal对象了");
}
try {
Thread.sleep(5000);
log.info("我是异步任务,我就是个打印!");
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("5s后异步任务终于执行完成");
}
@Async
public void asyncTaskAnnotation() {
asyncTask();
}
}
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。