本文章主要介绍了关于Spring Boot 2.0后中配置定时任务、线程池与多线程池。
一、配置基础的定时任务
1.pom.xml添加依赖
<!--定时任务quartz依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2.Application添加注解@EnableScheduling
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
System.out.println("start........");
SpringApplication.run(Application.class, args);
System.out.println("start........Success");
}
}
3.创建定时任务
新建类ExpertRecommendCountTask
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Created by Bruce on 2020/4/23
**/
@Component
@Configurable
public class ExpertRecommendCountTask {
private static final Logger logger = LoggerFactory.getLogger(ExpertRecommendCountTask.class);
/**
* 每两个小时执行一次
*/
@Scheduled(cron="0 0 0/2 * * ?")
// @Scheduled(cron="0/20 * * * * ?")
public void expertRecommendResultCount() {
logger.info("【---executor1】定时任务启动.....");
logger.info("【--executor1】定时任务结束.....");
}
}
至此配置基础的定时任务已经结束。
二、配置单线程池执行定时任务
1.在配置基础定时任务代码的基础之上添加 定时任务配置类SchedulerConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Created by Bruce on 2020/4/23
**/
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulerConfig {
/**
* * 1. 当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。
* * 2. 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。
* * 3. 查看任务队列是否已满,不满就将任务存储在任务队列中(SynchronousQueue同步队直接执行第四步),否则执行第四步。
* * 4. 查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
* @return
*/
@Bean
public Executor executor1() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
/**
* 线程名称前缀
*/
executor.setThreadNamePrefix("executor1-");
/**
* //此方法返回可用处理器的虚拟机的最大数量; 不小于1
*/
int core = Runtime.getRuntime().availableProcessors();
/**
* <!-- 最大线程数,默认为Integer.Max_value -->
* <property name="maxPoolSize" value="10" />
*/
executor.setMaxPoolSize(core * 2 + 1);
/**
* <!-- 核心线程数,默认为1 -->
* <property name="corePoolSize" value="3" />
*/
executor.setCorePoolSize(core);
/**
* <!-- 队列最大长度 >=mainExecutor.maxSize -->
* <property name="queueCapacity" value="25" />
* //如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueue
*/
executor.setQueueCapacity(40);
// /**
// * 除核心线程外的线程存活时间
// */
// executor.setKeepAliveSeconds(3);
/**
* <!-- 线程池对拒绝任务(无线程可用)的处理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃. -->
* <property name="rejectedExecutionHandler">
* <!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
* <!-- CallerRunsPolicy:若已达到待处理队列长度,将由主线程直接处理请求 -->
* <!-- DiscardOldestPolicy:抛弃旧的任务;会导致被丢弃的任务无法再次被执行 -->
* <!-- DiscardPolicy:抛弃当前任务;会导致被丢弃的任务无法再次被执行 -->
* <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
* </property>
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
2.修改定时任务类ExpertRecommendCountTask 中代码
在定时任务方法中添加注解@Async
添加@Async注解,表示该定时任务是异步执行的,因为上面线程池配置了名字,所以可以看到打印的日志是该线程池中的线程在执行任务,如果没有配置线程池的话会默认使用SimpleAsyncTaskExecutor,这个异步执行器每次都会开启一个子线程执行,性能消耗比较大,所以最好是自己配置线程池
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Created by Bruce on 2020/4/23
**/
@Component
@Configurable
public class ExpertRecommendCountTask {
private static final Logger logger = LoggerFactory.getLogger(ExpertRecommendCountTask.class);
/**
* 每两个小时执行一次
*/
@Async
@Scheduled(cron="0 0 0/2 * * ?")
// @Scheduled(cron="0/20 * * * * ?")
public void expertRecommendResultCount() {
logger.info("【---executor1】定时任务启动.....");
logger.info("【--executor1】定时任务结束.....");
}
}
至此配置单线程池执行定时任务执行完毕
三、配置多线程池执行定时任务
1.修改SchedulerConfig类,在原线程池‘executor1’基础之上,增加新的线程池‘executor2’。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Created by Bruce on 2020/4/23
**/
@Configuration
@EnableAsync
@EnableScheduling
public class SchedulerConfig {
/**
* * 1. 当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。
* * 2. 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。
* * 3. 查看任务队列是否已满,不满就将任务存储在任务队列中(SynchronousQueue同步队直接执行第四步),否则执行第四步。
* * 4. 查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。
* @return
*/
@Bean
public Executor executor1() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
/**
* 线程名称前缀
*/
executor.setThreadNamePrefix("executor1-");
/**
* //此方法返回可用处理器的虚拟机的最大数量; 不小于1
*/
int core = Runtime.getRuntime().availableProcessors();
/**
* <!-- 最大线程数,默认为Integer.Max_value -->
* <property name="maxPoolSize" value="10" />
*/
executor.setMaxPoolSize(core * 2 + 1);
/**
* <!-- 核心线程数,默认为1 -->
* <property name="corePoolSize" value="3" />
*/
executor.setCorePoolSize(core);
/**
* <!-- 队列最大长度 >=mainExecutor.maxSize -->
* <property name="queueCapacity" value="25" />
* //如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueue
*/
executor.setQueueCapacity(40);
// /**
// * 除核心线程外的线程存活时间
// */
// executor.setKeepAliveSeconds(3);
/**
* <!-- 线程池对拒绝任务(无线程可用)的处理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃. -->
* <property name="rejectedExecutionHandler">
* <!-- AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
* <!-- CallerRunsPolicy:若已达到待处理队列长度,将由主线程直接处理请求 -->
* <!-- DiscardOldestPolicy:抛弃旧的任务;会导致被丢弃的任务无法再次被执行 -->
* <!-- DiscardPolicy:抛弃当前任务;会导致被丢弃的任务无法再次被执行 -->
* <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
* </property>
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Bean
public Executor executor2() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("executor2-");
executor.setMaxPoolSize(20);
executor.setCorePoolSize(5);
executor.setQueueCapacity(0);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
2.修改ExpertRecommendCountTask类
在ExpertRecommendCountTask类中新增定时任务‘expertRecommendCount’,并指定线程池
修改原定时任务方法‘expertRecommendResultCount’上注解‘@Async’为‘@Async(“executor1”)’,指定使用上一步配置的线程池‘executor1’。
新增定时任务方法‘expertRecommendCount’,添加注解‘@Async(“executor2”)’,指定使用上一步配置的线程池‘executor2’。
因为上面在配置类里面初始化了两个线程池,所以会有两个线程池分别叫executor1和executor2被生成放到容器中,因为@Bean注解生成的对象默认就是和方法名相同的名字,而@Async注解是可以指定使用哪个线程池的。这样就可以在不同的线程池中执行不同的定时任务了。
注意:
①.没有配置自己的线程池时,会默认使用SimpleAsyncTaskExecutor。
②.如果项目中只配置了一个线程池,那么不需要显示指定使用这个线程池,spring也会自动使用用户配置的线程池,但是如果配置了多个就必须要显示指定,否则还是会使用默认的。
③.如果想要指定使用哪个线程池,可以使用@Async(“executor2”)显示指定。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Created by Bruce on 2020/4/23
**/
@Component
@Configurable
public class ExpertRecommendCountTask {
private static final Logger logger = LoggerFactory.getLogger(ExpertRecommendCountTask.class);
/**
* 每两个小时执行一次
*/
@Async("executor1")
@Scheduled(cron="0 0 0/2 * * ?")
// @Scheduled(cron="0/20 * * * * ?")
public void expertRecommendResultCount() {
logger.info("【---executor1】定时任务启动.....");
logger.info("【---executor1】定时任务结束.....");
}
/**
* 每天的1点、13点、15点、17点、19点、21点、23点都执行一次:0 0 1,13,15,17,19,21,23 * * ?
*/
@Async("executor2")
@Scheduled(cron="0 0 1,13,15,17,19,21,23 * * ?")
// @Scheduled(cron="0/20 * * * * ?")
public void expertRecommendCount() {
logger.info("【---executor2】定时任务启动.....");
logger.info("【---executor2】定时任务结束.....");
}
}
至此配置多线程池已经完毕。
以下是使用线程池需要注意的事项:
int corePoolSize:线程池维护线程的最小数量.
int maximumPoolSize:线程池维护线程的最大数量.
long keepAliveTime:空闲线程的存活时间.
TimeUnit unit: 时间单位,现有纳秒,微秒,毫秒,秒枚举值.
BlockingQueue<Runnable> workQueue:持有等待执行的任务队列.
RejectedExecutionHandler handler:
用来拒绝一个任务的执行,有两种情况会发生这种情况。
一是在execute方法中若addIfUnderMaximumPoolSize(command)为false,即线程池已经饱和;
二是在execute方法中, 发现runState!=RUNNING || poolSize == 0,即已经shutdown,就调用ensureQueuedTaskHandled(Runnable command),在该方法中有可能调用reject。
ThreadPoolExecutor池子的处理流程如下:
1)当池子大小小于corePoolSize就新建线程,并处理请求
2)当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理
3)当workQueue放不下新入的任务时,新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理
4)另外,当池子的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁
其会优先创建 CorePoolSiz 线程, 当继续增加线程时,先放入Queue中,当 CorePoolSiz 和 Queue 都满的时候,就增加创建新线程,当线程达到MaxPoolSize的时候,就会抛出错 误 org.springframework.core.task.TaskRejectedException
另外MaxPoolSize的设定如果比系统支持的线程数还要大时,会抛出java.lang.OutOfMemoryError: unable to create new native thread 异常。
**Reject拒绝策略预定义有四种:**
(1)ThreadPoolExecutor.AbortPolicy策略,是默认的策略,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
(2)ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃.
(3)ThreadPoolExecutor.DiscardPolicy策略,不能执行的任务将被丢弃.
(4)ThreadPoolExecutor.DiscardOldestPolicy策略,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程
参考:
JAVA线程池学习,ThreadPoolTaskExecutor和ThreadPoolExecutor有何区别?