spring定時器@Scheduled異步調(diào)用方式
前言
在springboot中的@schedule默認(rèn)的線程池中只有一個線程,所以如果在多個方法上加上@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í)行了
從打印的日志也可以看出來,兩個定時器共用一個線程。
此時就需要讓定時器使用異步的方式進(jìn)行,以下為實現(xiàn)方式:
使用自定義線程池實現(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)先級 taskExecutor.setDaemon(false);//是否為守護(hù)線程 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)當(dāng)前定時器任務(wù)還沒有執(zhí)行完一輪,下一輪就已經(jīng)開始了。
如果業(yè)務(wù)中需要用到上一次定時器的結(jié)果等情況,則會出現(xiàn)問題。
解決上一輪定時器任務(wù)未執(zhí)行完成,下一輪就開始執(zhí)行的問題
本人暫時想到的方式是通過加鎖的方式,當(dāng)上一輪未執(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ù)量小于默認(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)); } }
定時器類
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í)行,并且當(dāng)上一輪定時器沒有執(zhí)行完時,下一輪會等待上一輪完成后執(zhí)行。
總結(jié)
在Springboot中的@schedule默認(rèn)的線程池中只有一個線程,當(dāng)有多個定時器時,只會先執(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錯誤的原因以及解決方法,文中通過圖文以及代碼示例將這個錯誤介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05JAVA中通過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在SpringBoot中通過jasypt進(jìn)行加密解密的方法
今天小編就為大家分享一篇關(guān)于在SpringBoot中通過jasypt進(jìn)行加密解密的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01