spring定時(shí)器@Scheduled異步調(diào)用方式
前言
在springboot中的@schedule默認(rèn)的線程池中只有一個(gè)線程,所以如果在多個(gè)方法上加上@schedule的話,此時(shí)就會(huì)有多個(gè)任務(wù)加入到延時(shí)隊(duì)列中,因?yàn)橹挥幸粋€(gè)線程,所以任務(wù)只能被一個(gè)一個(gè)的執(zhí)行。
如果有多個(gè)定時(shí)器,而此時(shí)有定時(shí)器運(yùn)行時(shí)間過長(zhǎng),就會(huì)導(dǎo)致其他的定時(shí)器無(wú)法正常執(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í)行了")); } }
注意需要在啟動(dòng)類上加上@EnableScheduling
輸出臺(tái)
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í)行了
從打印的日志也可以看出來,兩個(gè)定時(shí)器共用一個(gè)線程。
此時(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: 180
import 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)先級(jí) 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í)行了")); } }
輸出臺(tái)結(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é)束
查看輸出臺(tái)可以看出定時(shí)器已經(jīng)異步執(zhí)行了。
但是這里會(huì)發(fā)現(xiàn)一個(gè)問題,可以發(fā)現(xiàn)當(dāng)前定時(shí)器任務(wù)還沒有執(zhí)行完一輪,下一輪就已經(jīng)開始了。
如果業(yè)務(wù)中需要用到上一次定時(shí)器的結(jié)果等情況,則會(huì)出現(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í)行了")); } }
輸出臺(tái)
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í)行開始
通過輸出臺(tái)可以看出下一輪的定時(shí)器會(huì)等待上一輪結(jié)束釋放鎖后才會(huì)執(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í)行了")); } }
輸出臺(tái)結(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í),下一輪會(huì)等待上一輪完成后執(zhí)行。
總結(jié)
在Springboot中的@schedule默認(rèn)的線程池中只有一個(gè)線程,當(dāng)有多個(gè)定時(shí)器時(shí),只會(huì)先執(zhí)行其中的一個(gè),其他定時(shí)器會(huì)加入到延時(shí)隊(duì)列中,等待被執(zhí)行。
Springboot實(shí)現(xiàn)定時(shí)器異步的方式有
- 通過自定義線程池的方式實(shí)現(xiàn)異步。
- 通過實(shí)現(xiàn)SchedulingConfigurer接口的方式實(shí)現(xiàn)異步。
這些僅為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot集成QQ第三方登陸的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot集成QQ第三方登陸的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11java面向?qū)ο蟮娜筇匦灾焕^承用法實(shí)例分析
這篇文章主要介紹了java面向?qū)ο蟮娜筇匦灾焕^承用法,結(jié)合實(shí)例形式分析了java面向?qū)ο蟪绦蛟O(shè)計(jì)中繼承的基本原理與具體使用方法,需要的朋友可以參考下2019-11-11java事件處理模型知識(shí)點(diǎn)總結(jié)
在本篇文章里小辮給大家分享的是一篇關(guān)于java事件處理模型知識(shí)點(diǎn)總結(jié)內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-01-01出現(xiàn)java.lang.UnsupportedClassVersionError錯(cuò)誤的原因以及解決方法
這篇文章主要給大家介紹了關(guān)于出現(xiàn)java.lang.UnsupportedClassVersionError錯(cuò)誤的原因以及解決方法,文中通過圖文以及代碼示例將這個(gè)錯(cuò)誤介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05JAVA中通過Redis實(shí)現(xiàn)延時(shí)任務(wù)demo實(shí)例
Redis在2.0版本時(shí)引入了發(fā)布訂閱(pub/sub)功能,在發(fā)布訂閱中有一個(gè)channel(頻道),與消息隊(duì)列中的topic(主題)類似,可以通過redis的發(fā)布訂閱者模式實(shí)現(xiàn)延時(shí)任務(wù)功能,實(shí)例中會(huì)議室預(yù)約系統(tǒng),用戶預(yù)約管理員審核后生效,如未審批,需要自動(dòng)變超期未處理,使用延時(shí)任務(wù)2024-08-08java中實(shí)體類和JSON對(duì)象之間相互轉(zhuǎn)化
Java中關(guān)于Json格式轉(zhuǎn)化Object,Map,Collection類型和String類型之間的轉(zhuǎn)化在我們實(shí)際項(xiàng)目中應(yīng)用的很是普遍和廣泛。最近工作的過程中也是經(jīng)常有,因此,自己封裝了一個(gè)類分享給大家。2015-05-05在SpringBoot中通過jasypt進(jìn)行加密解密的方法
今天小編就為大家分享一篇關(guān)于在SpringBoot中通過jasypt進(jìn)行加密解密的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01