介绍
在项目开发中,经常遇到定时任务,今天通过自定义多线程池总结一下SpringBoot默认实现的定时任务机制。
定时任务模板
pom依赖
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
//线程池用到
com.google.guava
guava
22.0
//@Slf4j注解用到
org.projectlombok
lombok
true
1.18.4
自定义线程池模板
package com.example.andya.demo.conf;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
/**
* @author Andya
* @create 2020-05-29 14:08
*/
@Configuration
public class ThreadPoolConfig {
public static String THREAD_NAME = "first-thread-pool-%d";
public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2 + 1;
public static int MAX_POOL_SIZE = 10;
public static int QUEUE_SIZE = 10;
/**
* 自定义消费队列线程池
*
* @return
*/
@Bean(value = "firstThreadPool")
public ExecutorService buildFirstThreadPool() {
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(THREAD_NAME).build();
/**
* 1. CallerRunsPolicy : 这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。
2. AbortPolicy : 对拒绝任务抛弃处理,并且抛出异常。
3. DiscardPolicy : 对拒绝任务直接无声抛弃,没有异常信息。
4. DiscardOldestPolicy : 对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。
*/
ExecutorService threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(QUEUE_SIZE),
threadFactory,
new ThreadPoolExecutor.AbortPolicy());
return threadPool;
}
}
定时任务模板
package com.example.andya.demo.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author Andya
* @create 2020-05-29 14:26
*/
@Service
@EnableScheduling
@Slf4j
public class TestThreadPool {
@Resource(name = "firstThreadPool")
private ExecutorService firstThreadPool;
@Scheduled(cron = "0 * * * * *")
public void test1SchedulerThreadPool() {
final CountDownLatch countDownLatch = new CountDownLatch(5);
log.info("Begin schedule-【1】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
for (int i = 0; i < 5; i++) {
firstThreadPool.execute(() -> {
countDownLatch.countDown();
log.info("schedule-【1】 , threadPool info: {}, countDownLatch info: {}", firstThreadPool.toString(), countDownLatch.toString());
});
}
try {
countDownLatch.await(5, TimeUnit.MINUTES);
} catch (InterruptedException e) {
log.error("schedule-【1】timeout, {}", e.getMessage());
} finally {
log.info("schedule-【1】 multi-threading countDownLatch count: {}", countDownLatch.getCount());
}
log.info("End schedule-【1】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
@Scheduled(cron = "0 * * * * *")
public void test2SchedulerThreadPool() {
final CountDownLatch countDownLatch = new CountDownLatch(5);
log.info("Begin schedule-【2】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
for (int i = 0; i < 5; i++) {
firstThreadPool.execute(() -> {
countDownLatch.countDown();
log.info("schedule-【2】, threadPool info {}, countDownLatch info: {}", firstThreadPool.toString(), countDownLatch.toString());
});
}
try {
countDownLatch.await(5, TimeUnit.MINUTES);
} catch (InterruptedException e) {
log.error("schedule-【2】timeout, {}", e.getMessage());
} finally {
log.info("schedule-【2】 multi-threading countDownLatch count: {}", countDownLatch.getCount());
}
log.info("End schedule-【2】 startTime: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
运行结果
2020-05-29 15:45:00.011 INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : Begin schedule-【2】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.018 INFO 6900 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 5, queued tasks = 0, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 4]
2020-05-29 15:45:00.019 INFO 6900 --- [t-thread-pool-1] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 4, queued tasks = 0, completed tasks = 1], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 3]
2020-05-29 15:45:00.019 INFO 6900 --- [t-thread-pool-2] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 3, queued tasks = 0, completed tasks = 2], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 2]
2020-05-29 15:45:00.019 INFO 6900 --- [t-thread-pool-3] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 2, queued tasks = 0, completed tasks = 3], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 1]
2020-05-29 15:45:00.019 INFO 6900 --- [t-thread-pool-4] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 5, active threads = 1, queued tasks = 0, completed tasks = 4], countDownLatch info: java.util.concurrent.CountDownLatch@522af59c[Count = 0]
2020-05-29 15:45:00.019 INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : schedule-【2】 multi-threading countDownLatch count: 0
2020-05-29 15:45:00.019 INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : End schedule-【2】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.020 INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : Begin schedule-【1】 startTime: 2020-05-29 15:45:00
2020-05-29 15:45:00.021 INFO 6900 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 5, queued tasks = 0, completed tasks = 5], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 4]
2020-05-29 15:45:00.023 INFO 6900 --- [t-thread-pool-5] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 4, queued tasks = 0, completed tasks = 6], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 3]
2020-05-29 15:45:00.024 INFO 6900 --- [t-thread-pool-6] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 2]
2020-05-29 15:45:00.024 INFO 6900 --- [t-thread-pool-7] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 1]
2020-05-29 15:45:00.025 INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : schedule-【1】 multi-threading countDownLatch count: 0
2020-05-29 15:45:00.025 INFO 6900 --- [t-thread-pool-8] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@26b894bd[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ced01a9[Count = 0]
2020-05-29 15:45:00.025 INFO 6900 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : End schedule-【1】 startTime: 2020-05-29 15:45:00
从上述结果中可以看出,虽然是test1SchedulerThreadPool()
和test2SchedulerThreadPool()
都是每分钟执行定时任务,但是明显两个方法没有并发执行,而是串行执行的。
并发定时器模板
通过增加一个配置类来并发执行定时任务。
package com.example.andya.demo.conf;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executors;
/**
* @author Andya
* @create 2020-05-29 15:52
*/
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
运行结果
2020-05-29 15:53:00.005 INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool : Begin schedule-【2】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.015 INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : Begin schedule-【1】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.019 INFO 3028 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 3]
2020-05-29 15:53:00.020 INFO 3028 --- [t-thread-pool-3] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 1]
2020-05-29 15:53:00.019 INFO 3028 --- [t-thread-pool-1] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 3]
2020-05-29 15:53:00.019 INFO 3028 --- [t-thread-pool-2] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 9, queued tasks = 1, completed tasks = 0], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 2]
2020-05-29 15:53:00.021 INFO 3028 --- [t-thread-pool-0] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 6, queued tasks = 0, completed tasks = 4], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 4]
2020-05-29 15:53:00.021 INFO 3028 --- [t-thread-pool-4] c.e.andya.demo.service.TestThreadPool : schedule-【2】, threadPool info java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 5, queued tasks = 0, completed tasks = 5], countDownLatch info: java.util.concurrent.CountDownLatch@630f649f[Count = 0]
2020-05-29 15:53:00.021 INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool : schedule-【2】 multi-threading countDownLatch count: 0
2020-05-29 15:53:00.022 INFO 3028 --- [pool-2-thread-2] c.e.andya.demo.service.TestThreadPool : End schedule-【2】 startTime: 2020-05-29 15:53:00
2020-05-29 15:53:00.022 INFO 3028 --- [t-thread-pool-5] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 4, queued tasks = 0, completed tasks = 6], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 3]
2020-05-29 15:53:00.022 INFO 3028 --- [t-thread-pool-6] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 3, queued tasks = 0, completed tasks = 7], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 2]
2020-05-29 15:53:00.024 INFO 3028 --- [t-thread-pool-7] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 2, queued tasks = 0, completed tasks = 8], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 1]
2020-05-29 15:53:00.024 INFO 3028 --- [t-thread-pool-8] c.e.andya.demo.service.TestThreadPool : schedule-【1】 , threadPool info: java.util.concurrent.ThreadPoolExecutor@5149f008[Running, pool size = 9, active threads = 2, queued tasks = 0, completed tasks = 8], countDownLatch info: java.util.concurrent.CountDownLatch@5ebb120e[Count = 0]
2020-05-29 15:53:00.024 INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : schedule-【1】 multi-threading countDownLatch count: 0
2020-05-29 15:53:00.024 INFO 3028 --- [pool-2-thread-1] c.e.andya.demo.service.TestThreadPool : End schedule-【1】 startTime: 2020-05-29 15:53:00
可以从运行结果中看到,test1SchedulerThreadPool()
和test2SchedulerThreadPool()
两个方法同时打印了Begin schedule...
信息,是并发定时。
@Async实现定时并发
除了上述这种实现SchedulingConfigurer
类来实现定时任务的并发,还可以通过@EnableAsync
及@Async
注解实现定时任务的并发。
package com.example.andya.demo.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
/**
* @author Andya
* @create 2020-04-20 22:50
*/
@Configuration
@EnableScheduling
@EnableAsync
public class StaticScheduleTask {
private Logger LOG = LoggerFactory.getLogger(StaticScheduleTask.class);
@Async
@Scheduled(cron = "${schedule.cron}")
public void firstTask() throws InterruptedException {
for (int i = 0; i < 5; i++) {
LOG.info("first task time: " + LocalDateTime.now() + "\r\nthread:" + Thread.currentThread().getName());
}
Thread.sleep(1000 * 10);
}
@Async
// @Scheduled(fixedDelay = 5000)
@Scheduled(cron = "0 * * * * *")
public void secondTask() throws InterruptedException {
for (int i = 0; i < 5; i++) {
LOG.info("second task time: " + LocalDateTime.now() + "\r\nthread:" + Thread.currentThread().getName());
}
Thread.sleep(1000 * 10);
}
}
运行结果:
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-1] c.e.andya.demo.util.StaticScheduleTask : second task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-1
2020-05-29 16:07:00.035 INFO 31428 --- [cTaskExecutor-2] c.e.andya.demo.util.StaticScheduleTask : first task time: 2020-05-29T16:07:00.035
thread:SimpleAsyncTaskExecutor-2