一、异步线程池
在Spring中存在一个AsyncConfigurer接口,它是一个可以配置异步线程池的接口,它的源码如下:
package org.springframework.scheduling.annotation;
import java.util.concurrent.Executor;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.lang.Nullable;
public interface AsyncConfigurer {
//获取线程池
@Nullable
default Executor getAsyncExecutor() {
return null;
}
//异步异常处理器
@Nullable
default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
其中getAsyncExecutor方法返回的是一个自定义的线程池,这样在开启异步时,线程池就会提供空闲线程来执行异步任务。因为线程中的业务逻辑可能抛出异常,所以还有一个处理异常的处理器方法,使得异常可以自定义处理。
下面看看如何配置使用异步线程池。
1、首先开发一个Java配置文件,实现AsyncConfigurer接口,并添加@EnableAsync注解,开启异步功能。
package com.scb.mongodemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
//定义线程池
ThreadPoolTaskExecutor taskExecutor=new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(10);
//线程池最大线程数
taskExecutor.setMaxPoolSize(30);
//线程队列最大线程数
taskExecutor.setQueueCapacity(2000);
//设置线程前缀
taskExecutor.setThreadNamePrefix("async-executor-");
//初始化
taskExecutor.initialize();
return taskExecutor;
}
}
除了上诉的设置外,我们还能设置线程池的拒绝策略。如:
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
拒绝策略常用的有以下四种:
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常(默认)。
- ThreadPoolExecutor.DiscardPolic:丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
- ThreadPoolExecutor.CallerRunsPolic:由调用线程处理该任务
在上诉我们定义线程池时,使用的是ThreadPoolTaskExecutor,而spring异步线程池的接口类,其实质是Java.util.concurrent.Executor
Spring 已经实现的异常线程池:
1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装
2、为了进行测试,定义一个异步服务接口
package com.scb.mongodemo.service;
public interface IAsyncService {
public void generateReport();
}
然后就是它的实现类
package com.scb.mongodemo.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncServiceImpl implements IAsyncService {
@Override
@Async
public void generateReport() {
System.out.println("report thread name:"+"["+Thread.currentThread().getName()+"]");
}
}
generateReport方法使用了@Async注解进行标注,这样在Spring的调用中,它就会使用线程池的线程去执行它。
3、异步控制器
package com.scb.mongodemo.controller;
import com.scb.mongodemo.service.IAsyncService;
import com.scb.mongodemo.service.ScheduleServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private IAsyncService asyncService;
@GetMapping("/page")
public String asyncPage(){
System.out.println("request thread name:"+"["+Thread.currentThread().getName()+"]");
asyncService.generateReport();
return "async";
}
}
4、运行程序,访问http://localhost:8080/async/page,结果截图如下:
二、定时器
在企业的实践生产中,可能需要使用一些定时任务。例如,在月末、季末和年末需要统计各种各样的报表,月表需要月末跑批量生成,年表需要年末跑批量生成,这样就需要制定不同的定时任务。
在Spring中使用定时器是比较简单的,首先在配置文件中加入@EnableScheduling注解,就能够使用注解驱动定时任务的机制,接着就可以通过@Scheduled注解去使用定时器。
1、在SpringBoot启动类上标注@EnableScheduling注解,启动定时器功能。
2、开发一个服务类,进行测试使用
package com.scb.mongodemo.service;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ScheduleServiceImpl {
int count=1;
@Scheduled(fixedRate = 1000)
public void job(){
System.out.println("["+Thread.currentThread().getName()+"]"+"->[job] run:"+count);
count++;
}
}
3、运行程序,结果截图如下:
上诉我们使用了@Scheduled的fixedRate参数去设置它每隔1秒便执行一次,除了fixedRate参数之外,@Scheduled注解还有以下参数: