spring定時器@Scheduled異步調(diào)用方式
前言
在springboot中的@schedule默認的線程池中只有一個線程,所以如果在多個方法上加上@schedule的話,此時就會有多個任務(wù)加入到延時隊列中,因為只有一個線程,所以任務(wù)只能被一個一個的執(zhí)行。
如果有多個定時器,而此時有定時器運行時間過長,就會導(dǎo)致其他的定時器無法正常執(zhí)行。
代碼示例
@Component
public class TestTimer {
@Scheduled(cron = "0/1 * * * * ? ")
public void test01()
{
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行開始"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行結(jié)束"));
}
@Scheduled(cron = "0/1 * * * * ? ")
public void test02()
{
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定時任務(wù)執(zhí)行了"));
}
}注意需要在啟動類上加上@EnableScheduling
輸出臺
2024-08-19 19:07:52.010 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:52test02定時任務(wù)執(zhí)行了
2024-08-19 19:07:52.010 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:52test01定時任務(wù)執(zhí)行開始
2024-08-19 19:07:55.024 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:52test01定時任務(wù)執(zhí)行結(jié)束
2024-08-19 19:07:55.024 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:55test02定時任務(wù)執(zhí)行了
2024-08-19 19:07:56.002 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:56test01定時任務(wù)執(zhí)行開始
2024-08-19 19:07:59.016 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:56test01定時任務(wù)執(zhí)行結(jié)束
2024-08-19 19:07:59.016 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:59test02定時任務(wù)執(zhí)行了
2024-08-19 19:08:00.014 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:08:00test01定時任務(wù)執(zhí)行開始
2024-08-19 19:08:03.022 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:08:00test01定時任務(wù)執(zhí)行結(jié)束
2024-08-19 19:08:03.022 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:08:03test02定時任務(wù)執(zhí)行了
從打印的日志也可以看出來,兩個定時器共用一個線程。
此時就需要讓定時器使用異步的方式進行,以下為實現(xiàn)方式:
使用自定義線程池實現(xiàn)異步代碼
配置文件
thread-pool:
config:
core-size: 8
max-size: 16
queue-capacity: 64
keep-alive-seconds: 180import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author qf
* @since 2024/08/20
*/
@Component
@ConfigurationProperties(prefix = "thread-pool.config")
@Data
public class TestThreadPoolConfig {
private Integer coreSize;
private Integer maxSize;
private Integer queueCapacity;
private Integer keepAliveSeconds;
}
線程池
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.RejectedExecutionHandler;
/**
* @author qf
*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {
@Autowired
private TestThreadPoolConfig testThreadPoolConfig;
@Bean(name = "testExecutor")
public ThreadPoolTaskExecutor testThreadPoolExecutor() {
return getAsyncTaskExecutor("test-Executor-", testThreadPoolConfig.getCoreSize(),
testThreadPoolConfig.getMaxSize(), testThreadPoolConfig.getQueueCapacity(),
testThreadPoolConfig.getKeepAliveSeconds(), null);
}
/**
* 統(tǒng)一異步線程池
*
* @param threadNamePrefix
* @param corePoolSize
* @param maxPoolSize
* @param queueCapacity
* @param keepAliveSeconds
* @param rejectedExecutionHandler 拒接策略 沒有填null
* @return
*/
private ThreadPoolTaskExecutor getAsyncTaskExecutor(String threadNamePrefix, int corePoolSize, int maxPoolSize, int queueCapacity, int keepAliveSeconds, RejectedExecutionHandler rejectedExecutionHandler) {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(corePoolSize);
taskExecutor.setMaxPoolSize(maxPoolSize);
taskExecutor.setQueueCapacity(queueCapacity);
taskExecutor.setThreadPriority(Thread.MAX_PRIORITY);//線程優(yōu)先級
taskExecutor.setDaemon(false);//是否為守護線程
taskExecutor.setKeepAliveSeconds(keepAliveSeconds);
taskExecutor.setThreadNamePrefix(threadNamePrefix);
taskExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
taskExecutor.initialize();//線程池初始化
return taskExecutor;
}
}定時器
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author qf
*/
@Slf4j
@Component
public class TestTimer {
@Async("testExecutor")
@Scheduled(cron = "0/1 * * * * ? ")
public void test01() {
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行開始"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行結(jié)束"));
}
@Async("testExecutor")
@Scheduled(cron = "0/1 * * * * ? ")
public void test02() {
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定時任務(wù)執(zhí)行了"));
}
}輸出臺結(jié)果
2024-08-20 19:33:20.020 INFO 4420 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:33:20test01定時任務(wù)執(zhí)行開始
2024-08-20 19:33:20.020 INFO 4420 --- [test-Executor-2] com.qbh.timer.TestTimer : 2024-08-20 19:33:20test02定時任務(wù)執(zhí)行了
2024-08-20 19:33:21.002 INFO 4420 --- [test-Executor-4] com.qbh.timer.TestTimer : 2024-08-20 19:33:21test02定時任務(wù)執(zhí)行了
2024-08-20 19:33:21.002 INFO 4420 --- [test-Executor-3] com.qbh.timer.TestTimer : 2024-08-20 19:33:21test01定時任務(wù)執(zhí)行開始
2024-08-20 19:33:22.015 INFO 4420 --- [test-Executor-5] com.qbh.timer.TestTimer : 2024-08-20 19:33:22test01定時任務(wù)執(zhí)行開始
2024-08-20 19:33:22.015 INFO 4420 --- [test-Executor-6] com.qbh.timer.TestTimer : 2024-08-20 19:33:22test02定時任務(wù)執(zhí)行了
2024-08-20 19:33:23.009 INFO 4420 --- [test-Executor-7] com.qbh.timer.TestTimer : 2024-08-20 19:33:23test01定時任務(wù)執(zhí)行開始
2024-08-20 19:33:23.009 INFO 4420 --- [test-Executor-8] com.qbh.timer.TestTimer : 2024-08-20 19:33:23test02定時任務(wù)執(zhí)行了
2024-08-20 19:33:23.025 INFO 4420 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:33:20test01定時任務(wù)執(zhí)行結(jié)束
2024-08-20 19:33:24.003 INFO 4420 --- [test-Executor-3] com.qbh.timer.TestTimer : 2024-08-20 19:33:21test01定時任務(wù)執(zhí)行結(jié)束
查看輸出臺可以看出定時器已經(jīng)異步執(zhí)行了。
但是這里會發(fā)現(xiàn)一個問題,可以發(fā)現(xiàn)當前定時器任務(wù)還沒有執(zhí)行完一輪,下一輪就已經(jīng)開始了。
如果業(yè)務(wù)中需要用到上一次定時器的結(jié)果等情況,則會出現(xiàn)問題。
解決上一輪定時器任務(wù)未執(zhí)行完成,下一輪就開始執(zhí)行的問題
本人暫時想到的方式是通過加鎖的方式,當上一輪未執(zhí)行完時,下一輪阻塞等待上一輪執(zhí)行。
改造定時器類
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author qf
*/
@Slf4j
@Component
public class TestTimer {
private final ReentrantLock timerLock = new ReentrantLock();
@Async("testExecutor")
@Scheduled(cron = "0/1 * * * * ? ")
public void test01() {
timerLock.lock();
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行開始"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
timerLock.unlock();//釋放鎖
}
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行結(jié)束"));
}
@Async("testExecutor")
@Scheduled(cron = "0/1 * * * * ? ")
public void test02() {
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定時任務(wù)執(zhí)行了"));
}
}輸出臺
2024-08-20 19:55:26.007 INFO 7752 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:55:26test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:26.007 INFO 7752 --- [test-Executor-2] com.qbh.timer.TestTimer : 2024-08-20 19:55:26test01定時任務(wù)執(zhí)行開始
2024-08-20 19:55:27.004 INFO 7752 --- [test-Executor-3] com.qbh.timer.TestTimer : 2024-08-20 19:55:27test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:28.014 INFO 7752 --- [test-Executor-5] com.qbh.timer.TestTimer : 2024-08-20 19:55:28test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:29.009 INFO 7752 --- [test-Executor-2] com.qbh.timer.TestTimer : 2024-08-20 19:55:26test01定時任務(wù)執(zhí)行結(jié)束
2024-08-20 19:55:29.009 INFO 7752 --- [test-Executor-4] com.qbh.timer.TestTimer : 2024-08-20 19:55:29test01定時任務(wù)執(zhí)行開始
2024-08-20 19:55:29.009 INFO 7752 --- [test-Executor-7] com.qbh.timer.TestTimer : 2024-08-20 19:55:29test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:30.004 INFO 7752 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:55:30test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:31.015 INFO 7752 --- [test-Executor-5] com.qbh.timer.TestTimer : 2024-08-20 19:55:31test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:32.009 INFO 7752 --- [test-Executor-4] com.qbh.timer.TestTimer : 2024-08-20 19:55:29test01定時任務(wù)執(zhí)行結(jié)束
2024-08-20 19:55:32.009 INFO 7752 --- [test-Executor-7] com.qbh.timer.TestTimer : 2024-08-20 19:55:32test02定時任務(wù)執(zhí)行了
2024-08-20 19:55:32.009 INFO 7752 --- [test-Executor-6] com.qbh.timer.TestTimer : 2024-08-20 19:55:32test01定時任務(wù)執(zhí)行開始
通過輸出臺可以看出下一輪的定時器會等待上一輪結(jié)束釋放鎖后才會執(zhí)行。
使用SchedulingConfigurer實現(xiàn)定時器異步調(diào)用
配置文件
import org.springframework.boot.autoconfigure.batch.BatchProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.lang.reflect.Method;
import java.util.concurrent.Executors;
@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
/**
* 計算出帶有Scheduled注解的方法數(shù)量,如果該數(shù)量小于默認池大?。?0),則使用默認線程池核心數(shù)大小20。
* @param taskRegistrar the registrar to be configured.
*/
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
Method[] methods = BatchProperties.Job.class.getMethods();
int defaultPoolSize = 20;
int corePoolSize = 0;
if (methods != null && methods.length > 0) {
System.out.println(methods.length);
for (Method method : methods) {
Scheduled annotation = method.getAnnotation(Scheduled.class);
if (annotation != null) {
corePoolSize++;
}
}
if (defaultPoolSize > corePoolSize) {
corePoolSize = defaultPoolSize;
}
}
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(corePoolSize));
}
}定時器類
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author qf
*/
@Slf4j
@Component
public class TestTimer {
@Scheduled(cron = "0/1 * * * * ? ")
public void test01() {
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行開始"));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定時任務(wù)執(zhí)行結(jié)束"));
}
@Scheduled(cron = "0/1 * * * * ? ")
public void test02() {
Date date = new Date();
log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定時任務(wù)執(zhí)行了"));
}
}輸出臺結(jié)果
2024-08-20 20:18:58.002 INFO 24744 --- [pool-2-thread-2] com.qbh.timer.TestTimer : 2024-08-20 20:18:58test01定時任務(wù)執(zhí)行開始
2024-08-20 20:18:58.002 INFO 24744 --- [pool-2-thread-1] com.qbh.timer.TestTimer : 2024-08-20 20:18:58test02定時任務(wù)執(zhí)行了
2024-08-20 20:18:59.014 INFO 24744 --- [pool-2-thread-1] com.qbh.timer.TestTimer : 2024-08-20 20:18:59test02定時任務(wù)執(zhí)行了
2024-08-20 20:19:00.006 INFO 24744 --- [pool-2-thread-3] com.qbh.timer.TestTimer : 2024-08-20 20:19:00test02定時任務(wù)執(zhí)行了
2024-08-20 20:19:01.005 INFO 24744 --- [pool-2-thread-1] com.qbh.timer.TestTimer : 2024-08-20 20:19:01test02定時任務(wù)執(zhí)行了
2024-08-20 20:19:01.005 INFO 24744 --- [pool-2-thread-2] com.qbh.timer.TestTimer : 2024-08-20 20:18:58test01定時任務(wù)執(zhí)行結(jié)束
2024-08-20 20:19:02.013 INFO 24744 --- [pool-2-thread-3] com.qbh.timer.TestTimer : 2024-08-20 20:19:02test01定時任務(wù)執(zhí)行開始
以上可以看出該方法也可以實現(xiàn)定時器異步執(zhí)行,并且當上一輪定時器沒有執(zhí)行完時,下一輪會等待上一輪完成后執(zhí)行。
總結(jié)
在Springboot中的@schedule默認的線程池中只有一個線程,當有多個定時器時,只會先執(zhí)行其中的一個,其他定時器會加入到延時隊列中,等待被執(zhí)行。
Springboot實現(xiàn)定時器異步的方式有
- 通過自定義線程池的方式實現(xiàn)異步。
- 通過實現(xiàn)SchedulingConfigurer接口的方式實現(xiàn)異步。
這些僅為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
出現(xiàn)java.lang.UnsupportedClassVersionError錯誤的原因以及解決方法
這篇文章主要給大家介紹了關(guān)于出現(xiàn)java.lang.UnsupportedClassVersionError錯誤的原因以及解決方法,文中通過圖文以及代碼示例將這個錯誤介紹的非常詳細,需要的朋友可以參考下2024-05-05
JAVA中通過Redis實現(xiàn)延時任務(wù)demo實例
Redis在2.0版本時引入了發(fā)布訂閱(pub/sub)功能,在發(fā)布訂閱中有一個channel(頻道),與消息隊列中的topic(主題)類似,可以通過redis的發(fā)布訂閱者模式實現(xiàn)延時任務(wù)功能,實例中會議室預(yù)約系統(tǒng),用戶預(yù)約管理員審核后生效,如未審批,需要自動變超期未處理,使用延時任務(wù)2024-08-08

