简介:
联想到异步,就会想到(Thread,Runnable,Callable,J.U.C数据同步处理机制)
目的:提高服务器处理性能。
在实际生产环境中,我们需要一个线程池来保护我们的应用程序,防止线程的无限制增长,给系统带来负荷。所以我们需要一个平衡点,就是线程池中线程的个数。
单线程池:一旦我们的线程消失了,线程池会立即为我们补充上一个线程。
多线程池:线程频繁创建和消失会损耗资源,引起性能下降。
Springboot中提供了很方便的多线程实现机制,开发者可以直接在控制器中以多线程的方式进行用户的请求响应。同时为了便于异步线程的管理,还提供有WebAsyncTask,DeferredResult线程管理类。
Callable实现异步线程的处理
它主要是进行异步返回实现的操作处理,实现比Runnable简单;
代码如下
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.Callable;
/**
* 异步线程的处理机制
*/
@RestController
@RequestMapping("/message/*")
@Slf4j
@Api(tags = "异步处理")
public class Async {
@RequestMapping("callable")
@ApiOperation("移除处理callable")
public Object message(String message){
log.info("外部线程:{}",Thread.currentThread().getName());
return new Callable<String>() {
@Override
public String call() throws Exception {
log.info("内部线程:{}",Thread.currentThread().getName());
return "[echo]"+message;
}
};
}
}
浏览器返回:
控制台返回:
对应返回的外部线程:就是tomcat容器所提供的外部线程,这类的线程是可以进行优化的。优化他的线程池大小和阻塞队列的大小等待。
在程序里面所开辟的子线程就是异步线程。在子线程执行完毕之后才开始进行最后的处理。
如果一直不断刷新,请求异步返回结果,会发现如下情况:
2021-11-30 09:19:07.369 INFO 10392 --- [nio-1999-exec-5] c.e.o.modules.modeler.security.MyFilter : swagger处理
2021-11-30 09:19:39.128 INFO 10392 --- [nio-1999-exec-3] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-3
2021-11-30 09:19:39.134 INFO 10392 --- [ task-1] com.example.oldguy.MyController.Async : 内部线程:task-1
2021-11-30 09:25:54.163 INFO 10392 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:25:54.164 INFO 10392 --- [ task-2] com.example.oldguy.MyController.Async : 内部线程:task-2
2021-11-30 09:25:54.338 INFO 10392 --- [nio-1999-exec-6] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-6
2021-11-30 09:25:54.339 INFO 10392 --- [ task-3] com.example.oldguy.MyController.Async : 内部线程:task-3
2021-11-30 09:25:54.521 INFO 10392 --- [nio-1999-exec-4] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-4
2021-11-30 09:25:54.521 INFO 10392 --- [ task-4] com.example.oldguy.MyController.Async : 内部线程:task-4
2021-11-30 09:25:54.704 INFO 10392 --- [nio-1999-exec-2] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-2
2021-11-30 09:25:54.705 INFO 10392 --- [ task-5] com.example.oldguy.MyController.Async : 内部线程:task-5
2021-11-30 09:25:54.890 INFO 10392 --- [io-1999-exec-10] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-10
2021-11-30 09:25:54.891 INFO 10392 --- [ task-6] com.example.oldguy.MyController.Async : 内部线程:task-6
2021-11-30 09:25:55.056 INFO 10392 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:25:55.057 INFO 10392 --- [ task-7] com.example.oldguy.MyController.Async : 内部线程:task-7
2021-11-30 09:25:55.232 INFO 10392 --- [nio-1999-exec-6] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-6
2021-11-30 09:25:55.233 INFO 10392 --- [ task-8] com.example.oldguy.MyController.Async : 内部线程:task-8
2021-11-30 09:25:55.416 INFO 10392 --- [nio-1999-exec-4] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-4
2021-11-30 09:25:55.417 INFO 10392 --- [ task-9] com.example.oldguy.MyController.Async : 内部线程:task-9
2021-11-30 09:25:55.599 INFO 10392 --- [nio-1999-exec-2] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-2
2021-11-30 09:25:55.600 INFO 10392 --- [ task-2] com.example.oldguy.MyController.Async : 内部线程:task-2
2021-11-30 09:25:55.785 INFO 10392 --- [io-1999-exec-10] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-10
2021-11-30 09:25:55.785 INFO 10392 --- [ task-3] com.example.oldguy.MyController.Async : 内部线程:task-3
2021-11-30 09:25:55.962 INFO 10392 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:25:55.962 INFO 10392 --- [ task-4] com.example.oldguy.MyController.Async : 内部线程:task-4
2021-11-30 09:25:56.153 INFO 10392 --- [nio-1999-exec-6] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-6
可以看出,springboot默认一共开启了10个大小的内部线程池。这样对于高级服务器不容易发挥出其特性,所以需要配置springboot的内部线程池。
代码如下:
package com.example.oldguy.configurations;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 优化springboot内部线程(异步线程)池的大小
*/
@Configuration
public class CustomerAsyncPoolConfig implements WebMvcConfigurer { //自定义的线程池配置
@Bean(name = "asyncPoolTaskExecutor") //springboot内部本身就有很多内部线程池提供,为了区分处理,加了名字
public ThreadPoolTaskExecutor getAsyncThreadPool(){ //异步线程池的关键
ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
t.setCorePoolSize(20);//内部线程的个数(物理线程的个数*2)有netty告诉我们的
t.setMaxPoolSize(200);//工作线程池的大小
t.setQueueCapacity(25); //如果我们的线程池已经被占用满了,需要设置一个延迟队列的大小(有25个用户队里了等待我们的操作完成)
t.setKeepAliveSeconds(200); //存活时间,时间短的话对我们的耗时操作非常不利。
t.setThreadNamePrefix("pansd -"); //配置前缀 个性配置
t.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy()
); //配置拒绝策略
t.initialize();//初始化
return t;
}
@Bean
public TimeoutCallableProcessingInterceptor getTimeoutInterceptor(){ //超时处理配置
return new TimeoutCallableProcessingInterceptor();
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) { //异步线程配置
configurer.setDefaultTimeout(10000); //配置超时时间
configurer.registerCallableInterceptors(this.getTimeoutInterceptor());//设置callable拦截器,一旦在处理超时的时候,默认处理
configurer.setTaskExecutor(this.getAsyncThreadPool()); //异步任务的执行配置
}
}
重启服务,可以看到控制台结果:
2021-11-30 09:46:36.728 INFO 39788 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 09:46:36.735 INFO 39788 --- [ pansd -1] com.example.oldguy.MyController.Async : 内部线程:pansd -1
2021-11-30 09:46:36.938 INFO 39788 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:46:36.939 INFO 39788 --- [ pansd -2] com.example.oldguy.MyController.Async : 内部线程:pansd -2
2021-11-30 09:46:37.097 INFO 39788 --- [nio-1999-exec-5] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-5
2021-11-30 09:46:37.097 INFO 39788 --- [ pansd -3] com.example.oldguy.MyController.Async : 内部线程:pansd -3
2021-11-30 09:46:37.260 INFO 39788 --- [nio-1999-exec-7] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-7
2021-11-30 09:46:37.262 INFO 39788 --- [ pansd -4] com.example.oldguy.MyController.Async : 内部线程:pansd -4
2021-11-30 09:46:37.418 INFO 39788 --- [io-1999-exec-10] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-10
2021-11-30 09:46:37.418 INFO 39788 --- [ pansd -5] com.example.oldguy.MyController.Async : 内部线程:pansd -5
2021-11-30 09:46:37.578 INFO 39788 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 09:46:37.578 INFO 39788 --- [ pansd -6] com.example.oldguy.MyController.Async : 内部线程:pansd -6
2021-11-30 09:46:37.744 INFO 39788 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:46:37.745 INFO 39788 --- [ pansd -7] com.example.oldguy.MyController.Async : 内部线程:pansd -7
2021-11-30 09:46:37.897 INFO 39788 --- [nio-1999-exec-5] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-5
2021-11-30 09:46:37.898 INFO 39788 --- [ pansd -8] com.example.oldguy.MyController.Async : 内部线程:pansd -8
2021-11-30 09:46:38.075 INFO 39788 --- [nio-1999-exec-7] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-7
2021-11-30 09:46:38.076 INFO 39788 --- [ pansd -9] com.example.oldguy.MyController.Async : 内部线程:pansd -9
2021-11-30 09:46:38.233 INFO 39788 --- [io-1999-exec-10] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-10
2021-11-30 09:46:38.234 INFO 39788 --- [ pansd -10] com.example.oldguy.MyController.Async : 内部线程:pansd -10
2021-11-30 09:46:38.393 INFO 39788 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 09:46:38.394 INFO 39788 --- [ pansd -11] com.example.oldguy.MyController.Async : 内部线程:pansd -11
2021-11-30 09:46:38.552 INFO 39788 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:46:38.553 INFO 39788 --- [ pansd -12] com.example.oldguy.MyController.Async : 内部线程:pansd -12
2021-11-30 09:46:38.712 INFO 39788 --- [nio-1999-exec-5] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-5
2021-11-30 09:46:38.713 INFO 39788 --- [ pansd -13] com.example.oldguy.MyController.Async : 内部线程:pansd -13
2021-11-30 09:46:38.864 INFO 39788 --- [nio-1999-exec-7] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-7
2021-11-30 09:46:38.865 INFO 39788 --- [ pansd -14] com.example.oldguy.MyController.Async : 内部线程:pansd -14
2021-11-30 09:46:39.041 INFO 39788 --- [io-1999-exec-10] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-10
2021-11-30 09:46:39.041 INFO 39788 --- [ pansd -15] com.example.oldguy.MyController.Async : 内部线程:pansd -15
2021-11-30 09:46:39.186 INFO 39788 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 09:46:39.187 INFO 39788 --- [ pansd -16] com.example.oldguy.MyController.Async : 内部线程:pansd -16
2021-11-30 09:46:39.337 INFO 39788 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:46:39.338 INFO 39788 --- [ pansd -17] com.example.oldguy.MyController.Async : 内部线程:pansd -17
2021-11-30 09:46:39.506 INFO 39788 --- [nio-1999-exec-5] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-5
2021-11-30 09:46:39.507 INFO 39788 --- [ pansd -18] com.example.oldguy.MyController.Async : 内部线程:pansd -18
2021-11-30 09:46:39.656 INFO 39788 --- [nio-1999-exec-7] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-7
2021-11-30 09:46:39.657 INFO 39788 --- [ pansd -19] com.example.oldguy.MyController.Async : 内部线程:pansd -19
2021-11-30 09:46:39.825 INFO 39788 --- [io-1999-exec-10] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-10
2021-11-30 09:46:39.825 INFO 39788 --- [ pansd -20] com.example.oldguy.MyController.Async : 内部线程:pansd -20
2021-11-30 09:46:39.976 INFO 39788 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 09:46:39.977 INFO 39788 --- [ pansd -1] com.example.oldguy.MyController.Async : 内部线程:pansd -1
2021-11-30 09:46:40.145 INFO 39788 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-8
2021-11-30 09:46:40.145 INFO 39788 --- [ pansd -2] com.example.oldguy.MyController.Async : 内部线程:pansd -2
2021-11-30 09:46:40.296 INFO 39788 --- [nio-1999-exec-5] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-5
2021-11-30 09:46:40.297 INFO 39788 --- [ pansd -3] com.example.oldguy.MyController.Async : 内部线程:pansd -3
2021-11-30 09:46:40.456 INFO 39788 --- [nio-1999-exec-7] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-7
2021-11-30 09:46:40.456 INFO 39788 --- [ pansd -4] com.example.oldguy.MyController.Async : 内部线程:pansd -4
WebAsyncTask的操作应用
WebAsyncTask是一个由spring提供的异步任务处理类,开发者可以直接在此类中配置要执行的请求处理的异步线程,同时也可以配置一个与之相关的超时管理及处理线程,这样在程序出现了超时以后,可以启动另外一个线程进行处理。
如果要在项目之中启动了异步线程,目的就是为了解决超时处理的问题。例如:某一用户请求在2s内执行完成,随着数据量的增长,无法完成,需要进行超时配置。
异步超时管理代码:
package com.example.oldguy.MyController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/**
* 异步线程的处理机制
*/
@RestController
@RequestMapping("/message/*")
@Slf4j
@Api(tags = "异步处理")
public class Async {
@RequestMapping("callable")
@ApiOperation("移除处理callable")
public Object message(String message) {
log.info("外部线程:{}", Thread.currentThread().getName());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
log.info("内部线程:{}", Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1); //模拟操作延时
return "[echo]" + message;
}
};
WebAsyncTask task = new WebAsyncTask(200, callable);
task.onTimeout(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("超时线程:{}", Thread.currentThread().getName());
return "[error]" + message;
}
});
return task;
}
}
线程超时了,webasync交给另外一个线程进行处理,返回task即可。页面返回:
控制台返回:
2021-11-30 10:18:02.486 INFO 15300 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 10:18:02.495 INFO 15300 --- [ pansd -1] com.example.oldguy.MyController.Async : 内部线程:pansd -1
2021-11-30 10:18:03.493 INFO 15300 --- [nio-1999-exec-5] com.example.oldguy.MyController.Async : 超时线程:http-nio-1999-exec-5
DefferredResult的操作应用+runnable
配合线程池完成异步返回数据处理。
package com.example.oldguy.MyController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncTask;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
/**
* 异步线程的处理机制
*/
@RestController
@RequestMapping("/message/*")
@Slf4j
@Api(tags = "异步处理")
public class Async {
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@RequestMapping("runnable")
@ApiOperation("异常处理Runnable")
public Object message(String message) {
log.info("外部线程:{}", Thread.currentThread().getName());
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
DeferredResult<String> result = new DeferredResult<>(6000L); //设置异步响应
result.onTimeout(new Runnable() {
@Override
public void run() {
log.info("超时线程:{}",Thread.currentThread().getName());
result.setResult("【请求超时】"+request.getRequestURI()); //超时路径
}
});
result.onCompletion(new Runnable() { //完成处理线程
@Override
public void run() {
log.info("完成线程:{}",Thread.currentThread().getName()); //日志输出
}
});
this.threadPoolTaskExecutor.execute(new Runnable() { //线程核心任务
@SneakyThrows
@Override
public void run() {
log.info("内部线程:{}",Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2);
result.setResult("[echo]"+message); //执行最终的响应
}
});
return result;
}
}
页面返回:
控制台返回:
2021-11-30 11:09:29.616 INFO 38772 --- [nio-1999-exec-6] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-6
2021-11-30 11:09:29.622 INFO 38772 --- [ pansd -1] com.example.oldguy.MyController.Async : 内部线程:pansd -1
2021-11-30 11:09:31.637 INFO 38772 --- [nio-1999-exec-8] com.example.oldguy.MyController.Async : 完成线程:http-nio-1999-exec-8
可以看到,页面输出的内容是内部线程的处理结果返回。
模拟请求超时:
页面返回结果:
控制台打印结果:
2021-11-30 11:14:33.313 INFO 31256 --- [nio-1999-exec-1] com.example.oldguy.MyController.Async : 外部线程:http-nio-1999-exec-1
2021-11-30 11:14:33.315 INFO 31256 --- [ pansd -1] com.example.oldguy.MyController.Async : 内部线程:pansd -1
2021-11-30 11:14:39.531 INFO 31256 --- [nio-1999-exec-2] com.example.oldguy.MyController.Async : 超时线程:http-nio-1999-exec-2
2021-11-30 11:14:39.550 INFO 31256 --- [nio-1999-exec-2] com.example.oldguy.MyController.Async : 完成线程:http-nio-1999-exec-2
springboot的异步任务调用
在异步编程中,除了可以使用异步编程进行用户请求访问之外,也可以开启一个新的异步任务执行某个耗时的操作,同时此任务与用户的响应无关,并且在用户已经响应完成后还可以继续执行。
定义外部线程任务:
package com.example.oldguy.mytask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class MyThreadTask {
@Async
public void startTaskHander(){
log.info("线程开启{}",Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程结束{}",Thread.currentThread().getName());
}
}
控制层代码:
@Autowired
private MyThreadTask task;
@RequestMapping("task")
@ApiOperation("task异步任务开启")
public Object messageTask(String message){
log.info("外部线程{}",Thread.currentThread().getName());
this.task.startTaskHander();
return "【echo】"+message;
}
页面返回:(立即响应)
控制台返回:(延迟后输出)
2021-11-30 13:42:50.060 INFO 28480 --- [nio-1999-exec-8] c.e.oldguy.myController.AsyncController : 外部线程http-nio-1999-exec-8
2021-11-30 13:42:50.063 INFO 28480 --- [nio-1999-exec-8] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2021-11-30 13:42:50.070 INFO 28480 --- [ pansd-1] com.example.oldguy.mytask.MyThreadTask : 线程开启pansd-1
2021-11-30 13:42:55.076 INFO 28480 --- [ pansd-1] com.example.oldguy.mytask.MyThreadTask : 线程结束pansd-1