spring定時(shí)器@Scheduled異步調(diào)用方式
前言
在springboot中的@schedule默認(rèn)的線程池中只有一個線程,所以如果在多個方法上加上@schedule的話,此時(shí)就會有多個任務(wù)加入到延時(shí)隊(duì)列中,因?yàn)橹挥幸粋€線程,所以任務(wù)只能被一個一個的執(zhí)行。
如果有多個定時(shí)器,而此時(shí)有定時(shí)器運(yùn)行時(shí)間過長,就會導(dǎo)致其他的定時(shí)器無法正常執(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(wù)執(zhí)行了"));
}
}注意需要在啟動類上加上@EnableScheduling
輸出臺
2024-08-19 19:07:52.010 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:52test02定時(shí)任務(wù)執(zhí)行了
2024-08-19 19:07:52.010 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:52test01定時(shí)任務(wù)執(zhí)行開始
2024-08-19 19:07:55.024 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:52test01定時(shí)任務(wù)執(zhí)行結(jié)束
2024-08-19 19:07:55.024 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:55test02定時(shí)任務(wù)執(zhí)行了
2024-08-19 19:07:56.002 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:56test01定時(shí)任務(wù)執(zhí)行開始
2024-08-19 19:07:59.016 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:56test01定時(shí)任務(wù)執(zhí)行結(jié)束
2024-08-19 19:07:59.016 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:07:59test02定時(shí)任務(wù)執(zhí)行了
2024-08-19 19:08:00.014 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:08:00test01定時(shí)任務(wù)執(zhí)行開始
2024-08-19 19:08:03.022 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:08:00test01定時(shí)任務(wù)執(zhí)行結(jié)束
2024-08-19 19:08:03.022 INFO 10372 --- [ scheduling-1] com.qbh.timer.TestTimer : 2024-08-19 19:08:03test02定時(shí)任務(wù)執(zhí)行了
從打印的日志也可以看出來,兩個定時(shí)器共用一個線程。
此時(shí)就需要讓定時(shí)器使用異步的方式進(jìn)行,以下為實(shí)現(xiàn)方式:
使用自定義線程池實(shí)現(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);//是否為守護(hù)線程
taskExecutor.setKeepAliveSeconds(keepAliveSeconds);
taskExecutor.setThreadNamePrefix(threadNamePrefix);
taskExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);
taskExecutor.initialize();//線程池初始化
return taskExecutor;
}
}定時(shí)器
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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(wù)執(zhí)行開始
2024-08-20 19:33:20.020 INFO 4420 --- [test-Executor-2] com.qbh.timer.TestTimer : 2024-08-20 19:33:20test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:33:21.002 INFO 4420 --- [test-Executor-4] com.qbh.timer.TestTimer : 2024-08-20 19:33:21test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:33:21.002 INFO 4420 --- [test-Executor-3] com.qbh.timer.TestTimer : 2024-08-20 19:33:21test01定時(shí)任務(wù)執(zhí)行開始
2024-08-20 19:33:22.015 INFO 4420 --- [test-Executor-5] com.qbh.timer.TestTimer : 2024-08-20 19:33:22test01定時(shí)任務(wù)執(zhí)行開始
2024-08-20 19:33:22.015 INFO 4420 --- [test-Executor-6] com.qbh.timer.TestTimer : 2024-08-20 19:33:22test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:33:23.009 INFO 4420 --- [test-Executor-7] com.qbh.timer.TestTimer : 2024-08-20 19:33:23test01定時(shí)任務(wù)執(zhí)行開始
2024-08-20 19:33:23.009 INFO 4420 --- [test-Executor-8] com.qbh.timer.TestTimer : 2024-08-20 19:33:23test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:33:23.025 INFO 4420 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:33:20test01定時(shí)任務(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定時(shí)任務(wù)執(zhí)行結(jié)束
查看輸出臺可以看出定時(shí)器已經(jīng)異步執(zhí)行了。
但是這里會發(fā)現(xiàn)一個問題,可以發(fā)現(xiàn)當(dāng)前定時(shí)器任務(wù)還沒有執(zhí)行完一輪,下一輪就已經(jīng)開始了。
如果業(yè)務(wù)中需要用到上一次定時(shí)器的結(jié)果等情況,則會出現(xiàn)問題。
解決上一輪定時(shí)器任務(wù)未執(zhí)行完成,下一輪就開始執(zhí)行的問題
本人暫時(shí)想到的方式是通過加鎖的方式,當(dāng)上一輪未執(zhí)行完時(shí),下一輪阻塞等待上一輪執(zhí)行。
改造定時(shí)器類
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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(wù)執(zhí)行了"));
}
}輸出臺
2024-08-20 19:55:26.007 INFO 7752 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:55:26test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:26.007 INFO 7752 --- [test-Executor-2] com.qbh.timer.TestTimer : 2024-08-20 19:55:26test01定時(shí)任務(wù)執(zhí)行開始
2024-08-20 19:55:27.004 INFO 7752 --- [test-Executor-3] com.qbh.timer.TestTimer : 2024-08-20 19:55:27test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:28.014 INFO 7752 --- [test-Executor-5] com.qbh.timer.TestTimer : 2024-08-20 19:55:28test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:29.009 INFO 7752 --- [test-Executor-2] com.qbh.timer.TestTimer : 2024-08-20 19:55:26test01定時(shí)任務(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定時(shí)任務(wù)執(zhí)行開始
2024-08-20 19:55:29.009 INFO 7752 --- [test-Executor-7] com.qbh.timer.TestTimer : 2024-08-20 19:55:29test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:30.004 INFO 7752 --- [test-Executor-1] com.qbh.timer.TestTimer : 2024-08-20 19:55:30test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:31.015 INFO 7752 --- [test-Executor-5] com.qbh.timer.TestTimer : 2024-08-20 19:55:31test02定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:32.009 INFO 7752 --- [test-Executor-4] com.qbh.timer.TestTimer : 2024-08-20 19:55:29test01定時(shí)任務(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定時(shí)任務(wù)執(zhí)行了
2024-08-20 19:55:32.009 INFO 7752 --- [test-Executor-6] com.qbh.timer.TestTimer : 2024-08-20 19:55:32test01定時(shí)任務(wù)執(zhí)行開始
通過輸出臺可以看出下一輪的定時(shí)器會等待上一輪結(jié)束釋放鎖后才會執(zhí)行。
使用SchedulingConfigurer實(shí)現(xiàn)定時(shí)器異步調(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 {
/**
* 計(jì)算出帶有Scheduled注解的方法數(shù)量,如果該數(shù)量小于默認(rèn)池大?。?0),則使用默認(rèn)線程池核心數(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));
}
}定時(shí)器類
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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(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定時(shí)任務(wù)執(zhí)行開始
以上可以看出該方法也可以實(shí)現(xiàn)定時(shí)器異步執(zhí)行,并且當(dāng)上一輪定時(shí)器沒有執(zhí)行完時(shí),下一輪會等待上一輪完成后執(zhí)行。
總結(jié)
在Springboot中的@schedule默認(rèn)的線程池中只有一個線程,當(dāng)有多個定時(shí)器時(shí),只會先執(zhí)行其中的一個,其他定時(shí)器會加入到延時(shí)隊(duì)列中,等待被執(zhí)行。
Springboot實(shí)現(xiàn)定時(shí)器異步的方式有
- 通過自定義線程池的方式實(shí)現(xiàn)異步。
- 通過實(shí)現(xiàn)SchedulingConfigurer接口的方式實(shí)現(xiàn)異步。
這些僅為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot集成QQ第三方登陸的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot集成QQ第三方登陸的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
java面向?qū)ο蟮娜筇匦灾焕^承用法實(shí)例分析
這篇文章主要介紹了java面向?qū)ο蟮娜筇匦灾焕^承用法,結(jié)合實(shí)例形式分析了java面向?qū)ο蟪绦蛟O(shè)計(jì)中繼承的基本原理與具體使用方法,需要的朋友可以參考下2019-11-11
出現(xiàn)java.lang.UnsupportedClassVersionError錯誤的原因以及解決方法
這篇文章主要給大家介紹了關(guān)于出現(xiàn)java.lang.UnsupportedClassVersionError錯誤的原因以及解決方法,文中通過圖文以及代碼示例將這個錯誤介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05
JAVA中通過Redis實(shí)現(xiàn)延時(shí)任務(wù)demo實(shí)例
Redis在2.0版本時(shí)引入了發(fā)布訂閱(pub/sub)功能,在發(fā)布訂閱中有一個channel(頻道),與消息隊(duì)列中的topic(主題)類似,可以通過redis的發(fā)布訂閱者模式實(shí)現(xiàn)延時(shí)任務(wù)功能,實(shí)例中會議室預(yù)約系統(tǒng),用戶預(yù)約管理員審核后生效,如未審批,需要自動變超期未處理,使用延時(shí)任務(wù)2024-08-08
java中實(shí)體類和JSON對象之間相互轉(zhuǎn)化
Java中關(guān)于Json格式轉(zhuǎn)化Object,Map,Collection類型和String類型之間的轉(zhuǎn)化在我們實(shí)際項(xiàng)目中應(yīng)用的很是普遍和廣泛。最近工作的過程中也是經(jīng)常有,因此,自己封裝了一個類分享給大家。2015-05-05
在SpringBoot中通過jasypt進(jìn)行加密解密的方法
今天小編就為大家分享一篇關(guān)于在SpringBoot中通過jasypt進(jìn)行加密解密的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01

