当某个请求执行非常耗时,当有大量访问该请求的时候,再访问请求其他服务时,会出现没有连接使用的情况。造成这种现象的主要原因是,容器中线程的数量是一定的,如果当所有线程都正在用来处理请求服务的时候,再有请求进来就没有多余的连接可用,只能拒绝连接。
SpringBoot异步机制可以使请求到了controller的时候,容器中的线程直接返回,同时使用系统内部的线程来执行耗时的服务,处理结束后果再将请求返回给客户端。
Servlet 3.0 新增了请求的异步处理,Spring 3.2 也在此基础上做了封装处理。
Spring通过任务执行器(TaskExecutor)来实现多线程和并发编程。使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor。
在 Spring Boot 框架中,可以在配置类中添加@EnableAsync开始对异步任务的支持,并将相应的方法使用@Async注解来声明为一个异步任务。
对于@Transactional、@Async等注解,Spring扫描到具有这些注解方法的类时,生成一个代理类,由代理类去执行,所以异步方法必须在类外部调用,并且@Async所修饰的不能是static方法。获取得到执行结果则可设置异步函数返回类型为Future
@Component
public class MyAsyncTask {
@Async
public Future<String> task1() throws InterruptedException{
return new AsyncResult<String>("task1执行完毕");
}
Controller的方法可以很快返回一个java.util.concurrent.Callable或DeferredResult,同时Servlet容器的主线程退出和释放,这并不意味着客户端收到了一个响应。
//Callable
@Controller
public class DefaultController {
@RequestMapping("/async/test")
@ResponseBody
public Callable<String> callable() {
//调用后生成一个非web线程来执行部分代码。
return new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(3 * 1000L);
return "finish" + System.currentTimeMillis();
}
};
}
}
//DeferredResult
@RestController
public class AsyncDeferredController {
private final TaskService taskService;
@Autowired
public AsyncDeferredController(TaskService taskService) {
this.taskService = taskService;
}
@RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")
public DeferredResult<String> executeSlowTask() {
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(taskService::execute).whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
return deferredResult;
}
}
Callable的其它示例:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new CallableImpl("my callable test!");
FutureTask<String> task = new FutureTask<>(callable);
new Thread(task).start();
//调用get()会阻塞主线程。
String result = task.get();
}
Callable和DeferredResult的区别:
Callable和DeferredResult效果都是释放容器线程,在另一个线程中运行长时间的任务。
不同的是谁管理执行任务的线程:
- Callable在执行线程完毕时返回。
- Deferredresult是通过设置返回对象值返回,可以在任何地方控制返回。
定时任务
1.在程序入口类上添加类注解 @EnableScheduling 来启用计划任务
2.在定时任务的类上加上类注解 @Component,在具体的定时任务方法上加上注解 @Scheduled 启动该定时任务。
@Scheduled(initialDelay=10000, fixedDelay=10000) //10 seconds, or use cron express
默认所有的@Scheduled都是串行执行的,并行需要继承SchedulingConfigurer类并重写其方法,实现并行任务。
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}