重學(xué)springBoot系列之異步任務(wù)與定時(shí)任務(wù)實(shí)現(xiàn)Async異步任務(wù)環(huán)境準(zhǔn)備同步調(diào)用異步調(diào)用異步回調(diào)為異步任務(wù)規(guī)劃線程池spring boot任務(wù)線程池自定義線程池優(yōu)雅地關(guān)閉線程池通過@Scheduled實(shí)現(xiàn)定時(shí)任務(wù)開啟定時(shí)任務(wù)方法不同定時(shí)方式的解析1.fixedDelay和fixedRate,單位是毫秒,它們的區(qū)別就是:cron表達(dá)式:靈活實(shí)現(xiàn)定時(shí)任務(wù)解決定時(shí)任務(wù)單線程運(yùn)行的問題quartz簡單定時(shí)任務(wù)(內(nèi)存持久化)引入對應(yīng)的 maven依賴創(chuàng)建一個(gè)任務(wù)類Job創(chuàng)建 Quartz 定時(shí)配置類深入解析核心概念SimpleTrigger and CronTriggerquartz動態(tài)定時(shí)任務(wù)(數(shù)據(jù)庫持久化)前言原理配置動態(tài)配置代碼實(shí)現(xiàn)分布式任務(wù)調(diào)度框架—xxl-job實(shí)現(xiàn)Async異步任務(wù)環(huán)境準(zhǔn)備

在 spring boot 入口類上配置 @enableasync 注解開啟異步處理。 創(chuàng)建任務(wù)抽象類 abstracttask,并分別配置三個(gè)任務(wù)方法 dotaskone(),dotasktwo(),dotaskthree()。
代碼語言:Javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
public abstract class AbstractTask { private static Random random = new Random(); public void doTaskOne() throws Exception { System.out.println("開始做任務(wù)一"); long start = currentTimeMillis(); sleep(random.nextInt(10000)); long end = currentTimeMillis(); System.out.println("完成任務(wù)一,耗時(shí):" + (end - start) + "毫秒"); } public void doTaskTwo() throws Exception { System.out.println("開始做任務(wù)二"); long start = currentTimeMillis(); sleep(random.nextInt(10000)); long end = currentTimeMillis(); System.out.println("完成任務(wù)二,耗時(shí):" + (end - start) + "毫秒"); } public void doTaskThree() throws Exception { System.out.println("開始做任務(wù)三"); long start = currentTimeMillis(); sleep(random.nextInt(10000)); long end = currentTimeMillis(); System.out.println("完成任務(wù)三,耗時(shí):" + (end - start) + "毫秒"); }}
同步調(diào)用
下面通過一個(gè)簡單示例來直觀的理解什么是同步調(diào)用:
定義 Task 類,繼承 AbstractTask,三個(gè)處理函數(shù)分別模擬三個(gè)執(zhí)行任務(wù)的操作,操作消耗時(shí)間隨機(jī)取(10 秒內(nèi))。代碼語言:JavaScript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Componentpublic class SyncTask extends AbstractTask {}
在 單元測試 用例中,注入 SyncTask 對象,并在測試用例中執(zhí)行 doTaskOne(),doTaskTwo(),doTaskThree() 三個(gè)方法。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@RunWith(SpringRunner.class)@SpringBootTestpublic class TaskTest { @Autowired private SyncTask task; @Test public void testSyncTasks() throws Exception { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); }}
執(zhí)行單元測試,可以看到類似如下輸出:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
開始做任務(wù)一完成任務(wù)一,耗時(shí):6720毫秒開始做任務(wù)二完成任務(wù)二,耗時(shí):6604毫秒開始做任務(wù)三完成任務(wù)三,耗時(shí):9448毫秒

任務(wù)一、任務(wù)二、任務(wù)三順序的執(zhí)行完了,換言之 doTaskOne(),doTaskTwo(),doTaskThree() 三個(gè)方法按調(diào)用順序的先后執(zhí)行完成。
異步調(diào)用
上述的 同步調(diào)用 雖然順利的執(zhí)行完了三個(gè)任務(wù),但是可以看到 執(zhí)行時(shí)間比較長,若這三個(gè)任務(wù)本身之間 不存在依賴關(guān)系,可以 并發(fā)執(zhí)行 的話,同步調(diào)用在 執(zhí)行效率 方面就比較差,可以考慮通過 異步調(diào)用 的方式來 并發(fā)執(zhí)行。
在Application啟動類上面加上@EnableAsync 創(chuàng)建 AsyncTask類,分別在方法上配置 @Async 注解,將原來的 同步方法 變?yōu)?異步方法。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Componentpublic class SyncTask extends AbstractTask { @Async @Override public void doTaskOne() throws Exception { super.doTaskOne(); } @Async @Override public void doTaskTwo() throws Exception { super.doTaskTwo(); } @Async @Override public void doTaskThree() throws Exception { super.doTaskThree(); }}
在 單元測試 用例中,注入 AsyncTask 對象,并在測試用例中執(zhí)行 doTaskOne(),doTaskTwo(),doTaskThree() 三個(gè)方法。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Autowiredprivate AsyncTask asyncTask;@Testpublic void testAsyncTasks() throws Exception { asyncTask.doTaskOne(); asyncTask.doTaskTwo(); asyncTask.doTaskThree();}
執(zhí)行單元測試,可以看到類似如下輸出:代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
開始做任務(wù)三開始做任務(wù)一開始做任務(wù)二
如果反復(fù)執(zhí)行單元測試,可能會遇到各種不同的結(jié)果,比如:
沒有任何任務(wù)相關(guān)的輸出 有部分任務(wù)相關(guān)的輸出 亂序的任務(wù)相關(guān)的輸出

原因是目前 doTaskOne(),doTaskTwo(),doTaskThree() 這三個(gè)方法已經(jīng) 異步并發(fā)執(zhí)行 了。主程序在 異步調(diào)用 之后,主程序并不會理會這三個(gè)函數(shù)是否執(zhí)行完成了,由于沒有其他需要執(zhí)行的內(nèi)容,所以程序就 自動結(jié)束 了,導(dǎo)致了任務(wù) 不完整 或是 沒有輸出 相關(guān)內(nèi)容的情況。
異步回調(diào)
為了讓 doTaskOne(),doTaskTwo(),doTaskThree() 能正常結(jié)束,假設(shè)我們需要統(tǒng)計(jì)一下三個(gè)任務(wù) 并發(fā)執(zhí)行 共耗時(shí)多少,這就需要等到上述三個(gè)函數(shù)都完成動用之后記錄時(shí)間,并計(jì)算結(jié)果。
那么我們?nèi)绾闻袛嗌鲜鋈齻€(gè) 異步調(diào)用 是否已經(jīng)執(zhí)行完成呢?我們需要使用 Future 來返回 異步調(diào)用 的 結(jié)果。
創(chuàng)建 AsyncCallBackTask 類,聲明 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三個(gè)方法,對原有的三個(gè)方法進(jìn)行包裝。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Componentpublic class AsyncCallBackTask extends AbstractTask { @Async public Future<String> doTaskOneCallback() throws Exception { super.doTaskOne(); return new AsyncResult<>("任務(wù)一完成"); } @Async public Future<String> doTaskTwoCallback() throws Exception { super.doTaskTwo(); return new AsyncResult<>("任務(wù)二完成"); } @Async public Future<String> doTaskThreeCallback() throws Exception { super.doTaskThree(); return new AsyncResult<>("任務(wù)三完成"); }}
在 單元測試 用例中,注入 AsyncCallBackTask 對象,并在測試用例中執(zhí)行 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三個(gè)方法。循環(huán)調(diào)用 Future 的 isDone() 方法等待三個(gè) 并發(fā)任務(wù) 執(zhí)行完成,記錄最終執(zhí)行時(shí)間。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Autowiredprivate AsyncCallBackTask asyncCallBackTask;@Testpublic void testAsyncCallbackTask() throws Exception { long start = currentTimeMillis(); Future<String> task1 = asyncCallBackTask.doTaskOneCallback(); Future<String> task2 = asyncCallBackTask.doTaskTwoCallback(); Future<String> task3 = asyncCallBackTask.doTaskThreeCallback(); // 三個(gè)任務(wù)都調(diào)用完成,退出循環(huán)等待 while (!task1.isDone() || !task2.isDone() || !task3.isDone()) { sleep(1000); } long end = currentTimeMillis(); System.out.println("任務(wù)全部完成,總耗時(shí):" + (end - start) + "毫秒");}
看看都做了哪些改變:
在測試用例一開始記錄開始時(shí)間; 在調(diào)用三個(gè)異步函數(shù)的時(shí)候,返回Future類型的結(jié)果對象; 在調(diào)用完三個(gè)異步函數(shù)之后,開啟一個(gè)循環(huán),根據(jù)返回的Future對象來判斷三個(gè)異步函數(shù)是否都結(jié)束了。若都結(jié)束,就結(jié)束循環(huán);若沒有都結(jié)束,就等1秒后再判斷。 跳出循環(huán)之后,根據(jù)結(jié)束時(shí)間 – 開始時(shí)間,計(jì)算出三個(gè)任務(wù)并發(fā)執(zhí)行的總耗時(shí)。

執(zhí)行一下上述的單元測試,可以看到如下結(jié)果:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
開始做任務(wù)三開始做任務(wù)一開始做任務(wù)二完成任務(wù)二,耗時(shí):2572毫秒完成任務(wù)一,耗時(shí):7333毫秒完成任務(wù)三,耗時(shí):7647毫秒任務(wù)全部完成,總耗時(shí):8013毫秒
可以看到,通過 異步調(diào)用,讓任務(wù)一、任務(wù)二、任務(wù)三 并發(fā)執(zhí)行,有效的 減少 了程序的 運(yùn)行總時(shí)間。
為異步任務(wù)規(guī)劃線程池Spring Boot任務(wù)線程池
線程池的作用
防止資源占用無限的擴(kuò)張 調(diào)用過程省去資源的創(chuàng)建和銷毀所占用的時(shí)間
在上一節(jié)中,我們的一個(gè)異步任務(wù)打開了一個(gè)線程,完成后銷毀。在高并發(fā)環(huán)境下,不斷的分配新資源,可能導(dǎo)致系統(tǒng)資源耗盡。所以為了避免這個(gè)問題,我們?yōu)楫惒饺蝿?wù)規(guī)劃一個(gè)線程池。當(dāng)然,如果沒有配置線程池的話,springboot會自動配置一個(gè)ThreadPoolTaskExecutor 線程池到bean當(dāng)中。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
# 核心線程數(shù)spring.task.execution.pool.core-size=8 # 最大線程數(shù)spring.task.execution.pool.max-size=16# 空閑線程存活時(shí)間spring.task.execution.pool.keep-alive=60s# 是否允許核心線程超時(shí)spring.task.execution.pool.allow-core-thread-timeout=true# 線程隊(duì)列數(shù)量spring.task.execution.pool.queue-capacity=100# 線程關(guān)閉等待spring.task.execution.shutdown.await-termination=falsespring.task.execution.shutdown.await-termination-period=# 線程名稱前綴spring.task.execution.thread-name-prefix=task-
自定義線程池
有的時(shí)候,我們希望將系統(tǒng)內(nèi)的一類任務(wù)放到一個(gè)線程池,另一類任務(wù)放到另外一個(gè)線程池,所以使用Spring Boot自帶的任務(wù)線程池就捉襟見肘了。下面介紹自定義線程池的方法。
創(chuàng)建一個(gè) 線程池配置類TaskConfiguration ,并配置一個(gè) 任務(wù)線程池對象taskExecutor。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Configurationpublic class TaskConfiguration { @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(new CallerRunsPolicy()); return executor; }}

上面我們通過使用 ThreadPoolTaskExecutor 創(chuàng)建了一個(gè) 線程池,同時(shí)設(shè)置了以下這些參數(shù):

Reject策略預(yù)定義有四種:
AbortPolicy,用于被拒絕任務(wù)的處理程序,它將拋出RejectedExecutionException。 CallerRunsPolicy,用于被拒絕任務(wù)的處理程序,它直接在execute方法的調(diào)用線程中運(yùn)行被拒絕的任務(wù)。 DiscardOldestPolicy,用于被拒絕任務(wù)的處理程序,它放棄最舊的未處理請求,然后重試execute。 DiscardPolicy,用于被拒絕任務(wù)的處理程序,默認(rèn)情況下它將丟棄被拒絕的任務(wù)。
創(chuàng)建 AsyncExecutorTask類,三個(gè)任務(wù)的配置和 AsyncTask 一樣,不同的是 @Async 注解需要指定前面配置的 線程池的名稱taskExecutor。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Componentpublic class AsyncExecutorTask extends AbstractTask { @Async("taskExecutor") public Future<String> doTaskOneCallback() throws Exception { super.doTaskOne(); System.out.println("任務(wù)一,當(dāng)前線程:" + Thread.currentThread().getName()); return new AsyncResult<>("任務(wù)一完成"); } @Async("taskExecutor") public Future<String> doTaskTwoCallback() throws Exception { super.doTaskTwo(); System.out.println("任務(wù)二,當(dāng)前線程:" + Thread.currentThread().getName()); return new AsyncResult<>("任務(wù)二完成"); } @Async("taskExecutor") public Future<String> doTaskThreeCallback() throws Exception { super.doTaskThree(); System.out.println("任務(wù)三,當(dāng)前線程:" + Thread.currentThread().getName()); return new AsyncResult<>("任務(wù)三完成"); }}
在 單元測試 用例中,注入 AsyncExecutorTask 對象,并在測試用例中執(zhí)行 doTaskOne(),doTaskTwo(),doTaskThree() 三個(gè)方法。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@SpringBootTestpublic class AsyncExecutorTaskTest { @Autowired private AsyncExecutorTask task; @Test public void testAsyncExecutorTask() throws Exception { task.doTaskOneCallback(); task.doTaskTwoCallback(); task.doTaskThreeCallback(); sleep(30 * 1000L); }}
執(zhí)行一下上述的 單元測試,可以看到如下結(jié)果:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
開始做任務(wù)一開始做任務(wù)三開始做任務(wù)二完成任務(wù)二,耗時(shí):3905毫秒任務(wù)二,當(dāng)前線程:taskExecutor-2完成任務(wù)一,耗時(shí):6184毫秒任務(wù)一,當(dāng)前線程:taskExecutor-1完成任務(wù)三,耗時(shí):9737毫秒任務(wù)三,當(dāng)前線程:taskExecutor-3
執(zhí)行上面的單元測試,觀察到 任務(wù)線程池 的 線程池名的前綴 被打印,說明 線程池 成功執(zhí)行 異步任務(wù)!
優(yōu)雅地關(guān)閉線程池
解決方案如下,重新設(shè)置線程池配置對象,新增線程池 setWaitForTasksToCompleteOnShutdown() 和 setAwaitTerminationSeconds() 配置:
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Bean("taskExecutor")public Executor taskExecutor() { ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler(); executor.setPoolSize(20); executor.setThreadNamePrefix("taskExecutor-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor;}
setWaitForTasksToCompleteOnShutdown(true): 該方法用來設(shè)置 線程池關(guān)閉 的時(shí)候 等待 所有任務(wù)都完成后,再繼續(xù) 銷毀 其他的 Bean,這樣這些 異步任務(wù) 的 銷毀 就會先于 數(shù)據(jù)庫連接池對象 的銷毀。 setAwaitTerminationSeconds(60): 該方法用來設(shè)置線程池中 任務(wù)的等待時(shí)間,如果超過這個(gè)時(shí)間還沒有銷毀就 強(qiáng)制銷毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住。
通過@Scheduled實(shí)現(xiàn)定時(shí)任務(wù)開啟定時(shí)任務(wù)方法
Scheduled定時(shí)任務(wù)是Spring boot自身提供的功能,所以不需要引入Maven依賴包
在項(xiàng)目入口main方法上加注解
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@EnableScheduling //開啟定時(shí)任務(wù)
不同定時(shí)方式的解析1.fixedDelay和fixedRate,單位是毫秒,它們的區(qū)別就是:

fixedRate就是每隔多長時(shí)間執(zhí)行一次。(開始——->X時(shí)間——>再開始)。如果間隔時(shí)間小于任務(wù)執(zhí)行時(shí)間,上一次任務(wù)執(zhí)行完成下一次任務(wù)就立即執(zhí)行。如果間隔時(shí)間大于任務(wù)執(zhí)行時(shí)間,就按照每隔X時(shí)間運(yùn)行一次。 而fixedDelay是當(dāng)任務(wù)執(zhí)行完畢后一段時(shí)間再次執(zhí)行。(開始—>結(jié)束(隔一分鐘)開始—–>結(jié)束)。上一次執(zhí)行任務(wù)未完成,下一次任務(wù)不會開始
cron表達(dá)式:靈活
舉例說明

第一位,表示秒,取值0-59 第二位,表示分,取值0-59 第三位,表示小時(shí),取值0-23 第四位,日期天/日,取值1-31 第五位,日期月份,取值1-12 第六位,星期,取值1-7,星期一,星期二…,注:不是第1周,第二周的意思,另外:1表示星期天,2表示星期一。 第七位,年份,可以留空,取值1970-2099
cron中,還有一些特殊的符號,含義如下:
(*)星號:可以理解為每的意思,每秒,每分,每天,每月,每年… (?)問號:問號只能出現(xiàn)在日期和星期這兩個(gè)位置。 (-)減號:表達(dá)一個(gè)范圍,如在小時(shí)字段中使用“10-12”,則表示從10到12點(diǎn),即10,11,12 (,)逗號:表達(dá)一個(gè)列表值,如在星期字段中使用“1,2,4”,則表示星期一,星期二,星期四 (/)斜杠:如:x/y,x是開始值,y是步長,比如在第一位(秒)0/15就是,從0秒開始,每15秒,最后就是0,15,30,45,60 另:/y,等同于0/y
cron表達(dá)式在線:http://cron.qqe2.com/
實(shí)現(xiàn)定時(shí)任務(wù)代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Componentpublic class ScheduledJobs { //表示方法執(zhí)行完成后5秒再開始執(zhí)行 @Scheduled(fixedDelay=5000) public void fixedDelayJob() throws InterruptedException{ System.out.println("fixedDelay 開始:" + new Date()); Thread.sleep(10 * 1000); System.out.println("fixedDelay 結(jié)束:" + new Date()); } //表示每隔3秒 @Scheduled(fixedRate=3000) public void fixedRateJob()throws InterruptedException{ System.out.println("===========fixedRate 開始:" + new Date()); Thread.sleep(5 * 1000); System.out.println("===========fixedRate 結(jié)束:" + new Date()); } //表示每隔10秒執(zhí)行一次 @Scheduled(cron="0/10 * * * * ? ") public void cronJob(){ System.out.println("=========================== ...>>cron...." + new Date()); }}
運(yùn)行結(jié)果如下:從運(yùn)行結(jié)果上看,并未按照預(yù)期的時(shí)間規(guī)律運(yùn)行。仔細(xì)看線程打印,竟然所有的定時(shí)任務(wù)使用的都是一個(gè)線程,所以彼此互相影響。
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
===========fixedRate 結(jié)束:Tue Jul 09 19:53:04 CST 2019pool-1-thread-1fixedDelay 開始:Tue Jul 09 19:53:04 CST 2019pool-1-thread-1fixedDelay 結(jié)束:Tue Jul 09 19:53:14 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:14 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:16 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:16 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:18 CST 2019pool-1-thread-1=========================== ...>>cron....Tue Jul 09 19:53:18 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:18 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:20 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:20 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:22 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:22 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:24 CST 2019pool-1-thread-1fixedDelay 開始:Tue Jul 09 19:53:24 CST 2019pool-1-thread-1fixedDelay 結(jié)束:Tue Jul 09 19:53:34 CST 2019pool-1-thread-1=========================== ...>>cron....Tue Jul 09 19:53:34 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:34 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:36 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:36 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:38 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:38 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:40 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:40 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:42 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:42 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:44 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:44 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:46 CST 2019pool-1-thread-1===========fixedRate 開始:Tue Jul 09 19:53:46 CST 2019pool-1-thread-1===========fixedRate 結(jié)束:Tue Jul 09 19:53:48 CST 2019pool-1-thread-1fixedDelay 開始:Tue Jul 09 19:53:48 CST 2019pool-1-thread-1fixedDelay 結(jié)束:Tue Jul 09 19:53:58 CST 2019pool-1-thread-1=========================== ...>>cron....Tue Jul 09 19:53:58 CST 2019pool-1-thread-1
解決定時(shí)任務(wù)單線程運(yùn)行的問題代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Configuration@EnableSchedulingpublic class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(scheduledTaskExecutor()); } @Bean public Executor scheduledTaskExecutor() { return Executors.newScheduledThreadPool(3); //指定線程池大小 }}
再次運(yùn)行上面的程序,運(yùn)行時(shí)間規(guī)律就符合期望了。
quartz簡單定時(shí)任務(wù)(內(nèi)存持久化)
Quartz是OpenSymphony開源組織在工作計(jì)劃-定時(shí)任務(wù)領(lǐng)域的另一個(gè)開源項(xiàng)目。它是完全由Java開發(fā)的,可用于執(zhí)行預(yù)定任務(wù)。它類似于java.util.Timer定時(shí)器。但是與timer相比,quartz增加了許多功能。
引入對應(yīng)的 maven依賴
在 springboot2.0 后官方添加了 Quartz 框架的依賴,所以只需要在 pom 文件當(dāng)中引入
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
<!--引入quartz定時(shí)框架--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
創(chuàng)建一個(gè)任務(wù)類Job
首先,我們需要定義一個(gè)接口來實(shí)現(xiàn)計(jì)時(shí)功能。我們可以將其稱為任務(wù)(或任務(wù)),例如:定期發(fā)送電子郵件的任務(wù),重新啟動機(jī)器的任務(wù)以及在優(yōu)惠券到期時(shí)發(fā)送SMS提醒的任務(wù)。

由于 springboot2.0 自動進(jìn)行了依賴所以創(chuàng)建的定時(shí)任務(wù)類直接繼承 QuzrtzJobBean 就可以了,新建一個(gè)定時(shí)任務(wù)類:QuartzSimpleTask
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
public class QuartzSimpleTask extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println("quartz簡單的定時(shí)任務(wù)執(zhí)行時(shí)間:"+new Date().toLocaleString()); }}
創(chuàng)建 Quartz 定時(shí)配置類
還需要一個(gè)可以觸發(fā)任務(wù)執(zhí)行的觸發(fā)器。觸發(fā)器觸發(fā)器的基本功能是指定作業(yè)的執(zhí)行時(shí)間,執(zhí)行間隔和運(yùn)行時(shí)間。

如何結(jié)合工作與觸發(fā)?也就是說,如何分配觸發(fā)器以執(zhí)行指定的作業(yè)?此時(shí),需要一個(gè)Schedule來實(shí)現(xiàn)此功能。

將之前創(chuàng)建的定時(shí)任務(wù)添加到定時(shí)調(diào)度里面
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Configurationpublic class QuartzSimpleConfig { //指定具體的定時(shí)任務(wù)類 @Bean public JobDetail uploadTaskDetail() { return JobBuilder.newJob(QuartzSimpleTask.class) .withIdentity("QuartzSimpleTask") .storeDurably().build(); } @Bean public Trigger uploadTaskTrigger() { //這里設(shè)定觸發(fā)執(zhí)行的方式 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("*/5 * * * * ?"); // 返回任務(wù)觸發(fā)器 return TriggerBuilder.newTrigger().forJob(uploadTaskDetail()) .withIdentity("QuartzSimpleTask") .withSchedule(scheduleBuilder) .build(); }}
最后運(yùn)行項(xiàng)目查看效果,”*/5 * * * * ?”表示定時(shí)任務(wù),每隔5秒鐘執(zhí)行一次。

只要項(xiàng)目一啟動,定時(shí)任務(wù)就會開始執(zhí)行
深入解析核心概念 Job:一個(gè)僅包含一個(gè)void execute(JobExecutionContext context)Abstract方法的簡單接口。在實(shí)際開發(fā)中,要執(zhí)行的任務(wù)是通過實(shí)現(xiàn)接口自定義實(shí)現(xiàn)的。JobExecutionContext提供調(diào)度上下文信息。代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
public interface Job { void execute(JobExecutionContext context) throws JobExecutionException;}
JobDetail:包含多個(gè)構(gòu)造函數(shù),最常用的是JobDetail(String name, String group,Class jobClass),Jobclass是實(shí)現(xiàn)作業(yè)接口的類,name是調(diào)度程序中任務(wù)的名稱,group是調(diào)度程序中任務(wù)的組名。默認(rèn)組名稱為Scheduler.DEFAULT_GROUP。 Trigger:描述觸發(fā)作業(yè)執(zhí)行的時(shí)間規(guī)則的類。包含:
SimpleTrigger:一次或固定間隔時(shí)間段的觸發(fā)規(guī)則。
CronTrigger:通過cron表達(dá)式描述更復(fù)雜的觸發(fā)規(guī)則。
Calendar:Quartz 提供的Calendar類。觸發(fā)器可以與多個(gè)Calendar關(guān)聯(lián)以排除特殊日期。
Scheduler:代表獨(dú)立于Quartz 的運(yùn)行容器。在Scheduler 中注冊了Trigger和JobDetail。它們在調(diào)度程序中具有自己的名稱(名稱)和組名稱(Group)。觸發(fā)器和JobDetail名稱和組名稱的組合必須唯一,但是觸發(fā)器名稱和組名稱的組合可以與JobDetail相同。一個(gè)Job可以綁定到多個(gè)觸發(fā)器,也可以不綁定。
Job還具有一個(gè)子接口:statefuljob,這是一個(gè)沒有方法的標(biāo)簽接口,表示有狀態(tài)任務(wù)。
無狀態(tài)任務(wù):它具有jobdatamap復(fù)制,因此可以并發(fā)運(yùn)行; 有狀態(tài)任務(wù)statefuljob:共享一個(gè)jobdatamap,并且將保存對jobdatamap的每次修改。因此,前一個(gè)有statefuljob將阻止下一個(gè)statefuljob。
SimpleTrigger and CronTrigger SimpleTrigger可以在指定的時(shí)間段內(nèi)執(zhí)行一個(gè)Job任務(wù),也可以在一個(gè)時(shí)間段內(nèi)多次執(zhí)行。 CronTrigger功能非常強(qiáng)大,它基于Calendar進(jìn)行作業(yè)調(diào)度,并且可以比simpletrigger更精確地指定間隔,因此crotrigger比simpletrigger更常用。Crotrigger基于cron表達(dá)式。
首先,讓我們了解cron表達(dá)式: 由七個(gè)子表達(dá)式組成的字符串的格式如下:
[秒] [分鐘] [小時(shí)] [天] [月] [周] [年]
例如:00:00:00?* 10,11,12 1#5 2018 ,表示2018年10月,11月和12月的第一周星期五的00:00:00。看上去不是很容易書寫與記憶,但是我們可以通過網(wǎng)絡(luò)上的在線Cron表達(dá)式生成工具,來幫助我們寫表達(dá)式:在線生成cron表達(dá)式的工具:http://cron.qqe2.com/

特殊字符的含義如下:
星號( * ):可在所有字段中使用以指示相應(yīng)時(shí)域中的每次時(shí)間。例如,分鐘字段中的*表示“每分鐘”;
問號(?):此字符僅在日期和星期字段中使用。通常將其指定為“無意義的值”,等同于點(diǎn)字符;
減號(-):表示范圍。如果在小時(shí)字段中使用“ 10-12”,則表示10到12,即10、11、12;
逗號(,):表示列表值。如果在星期字段中使用“星期一,星期三,星期五”,則表示星期一,星期三和星期五;
斜線(/):X / Y表示相等的步長序列,其中X為起始值,y為增量步長值。如果在分鐘字段中使用0/15,則表示0、15、30和45秒,而5/15在分鐘字段中表示5、20、35、50,也可以使用* / y,這等效到0 / y;
quartz動態(tài)定時(shí)任務(wù)(數(shù)據(jù)庫持久化)前言
在項(xiàng)目開發(fā)過程當(dāng)中,某些定時(shí)任務(wù),可能在運(yùn)行一段時(shí)間之后,就不需要了,或者需要修改下定時(shí)任務(wù)的執(zhí)行時(shí)間等等。
需要在代碼當(dāng)中進(jìn)行修改然后重新打包發(fā)布,很麻煩。使用Quartz來實(shí)現(xiàn)的話不需要重新修改代碼而達(dá)到要求。
原理 使用quartz提供的API完成配置任務(wù)的增刪改查 將任務(wù)的配置保存在數(shù)據(jù)庫中
配置
application.yml
在上面已經(jīng)引入了maven依賴包,這里不再重復(fù)。直接spring屬性下面加入quartz配置信息
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
spring: datasource: url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false username: test password: 4rfv$RFV driver-class-name: com.mysql.jdbc.Driver quartz: job-store-type: JDBC #數(shù)據(jù)庫存儲quartz任務(wù)配置 jdbc: initialize-schema: NEVER #自動初始化表結(jié)構(gòu),第一次啟動的時(shí)候這里寫always

動態(tài)配置代碼實(shí)現(xiàn)
第一步 創(chuàng)建一個(gè)定時(shí)任務(wù)相關(guān)實(shí)體類用于保存定時(shí)任務(wù)相關(guān)信息到數(shù)據(jù)庫當(dāng)中
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Datapublic class QuartzBean { /** 任務(wù)id */ private String id; /** 任務(wù)名稱 */ private String jobName; /** 任務(wù)執(zhí)行類 */ private String jobClass; /** 任務(wù)狀態(tài) 啟動還是暫停*/ private Integer status; /** 任務(wù)運(yùn)行時(shí)間表達(dá)式 */ private String cronExpression;}
第二步 創(chuàng)建定時(shí)任務(wù)暫停,修改,啟動,單次啟動工具類
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
public class QuartzUtils { /** * 創(chuàng)建定時(shí)任務(wù) 定時(shí)任務(wù)創(chuàng)建之后默認(rèn)啟動狀態(tài) * @param scheduler 調(diào)度器 * @param quartzBean 定時(shí)任務(wù)信息類 */ @SuppressWarnings("unchecked") public static void createScheduleJob(Scheduler scheduler, QuartzBean quartzBean) throws ClassNotFoundException, SchedulerException { //獲取到定時(shí)任務(wù)的執(zhí)行類 必須是類的絕對路徑名稱 //定時(shí)任務(wù)類需要是job類的具體實(shí)現(xiàn) QuartzJobBean是job的抽象類。 Class<? extends Job> jobClass = (Class<? extends Job>) Class.forName(quartzBean.getJobClass()); // 構(gòu)建定時(shí)任務(wù)信息 JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(quartzBean.getJobName()).build(); // 設(shè)置定時(shí)任務(wù)執(zhí)行方式 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression()); // 構(gòu)建觸發(fā)器trigger CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(quartzBean.getJobName()).withSchedule(scheduleBuilder).build(); scheduler.scheduleJob(jobDetail, trigger); } /** * 根據(jù)任務(wù)名稱暫停定時(shí)任務(wù) * @param scheduler 調(diào)度器 * @param jobName 定時(shí)任務(wù)名稱 */ public static void pauseScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException { JobKey jobKey = JobKey.jobKey(jobName); scheduler.pauseJob(jobKey); } /** * 根據(jù)任務(wù)名稱恢復(fù)定時(shí)任務(wù) * @param scheduler 調(diào)度器 * @param jobName 定時(shí)任務(wù)名稱 */ public static void resumeScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException { JobKey jobKey = JobKey.jobKey(jobName); scheduler.resumeJob(jobKey); } /** * 根據(jù)任務(wù)名稱立即運(yùn)行一次定時(shí)任務(wù) * @param scheduler 調(diào)度器 * @param jobName 定時(shí)任務(wù)名稱 */ public static void runOnce(Scheduler scheduler, String jobName) throws SchedulerException { JobKey jobKey = JobKey.jobKey(jobName); scheduler.triggerJob(jobKey); } /** * 更新定時(shí)任務(wù) * @param scheduler 調(diào)度器 * @param quartzBean 定時(shí)任務(wù)信息類 */ public static void updateScheduleJob(Scheduler scheduler, QuartzBean quartzBean) throws SchedulerException { //獲取到對應(yīng)任務(wù)的觸發(fā)器 TriggerKey triggerKey = TriggerKey.triggerKey(quartzBean.getJobName()); //設(shè)置定時(shí)任務(wù)執(zhí)行方式 CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzBean.getCronExpression()); //重新構(gòu)建任務(wù)的觸發(fā)器trigger CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build(); //重置對應(yīng)的job scheduler.rescheduleJob(triggerKey, trigger); } /** * 根據(jù)定時(shí)任務(wù)名稱從調(diào)度器當(dāng)中刪除定時(shí)任務(wù) * @param scheduler 調(diào)度器 * @param jobName 定時(shí)任務(wù)名稱 */ public static void deleteScheduleJob(Scheduler scheduler, String jobName) throws SchedulerException { JobKey jobKey = JobKey.jobKey(jobName); scheduler.deleteJob(jobKey); }}
控制層
代碼語言:javascript代碼運(yùn)行次數(shù):0運(yùn)行復(fù)制
@Controller@RequestMapping("/quartz/job/")public class QuartzController { //注入任務(wù)調(diào)度 @Resource private Scheduler scheduler; @PostMapping("/create") @ResponseBody public String createJob(@RequestBody QuartzBean quartzBean) throws SchedulerException, ClassNotFoundException { QuartzUtils.createScheduleJob(scheduler,quartzBean); return "已創(chuàng)建任務(wù)";//這里return不是生產(chǎn)級別代碼,測試簡單寫一下 } @PostMapping("/pause") @ResponseBody public String pauseJob(String jobName) throws SchedulerException { QuartzUtils.pauseScheduleJob (scheduler,jobName); return "已暫停成功";//這里return不是生產(chǎn)級別代碼,測試簡單寫一下 } @PostMapping("/run") @ResponseBody public String runOnce(String jobName) throws SchedulerException { QuartzUtils.runOnce (scheduler,jobName); return "運(yùn)行任務(wù)" + jobName + "成功";//這里return不是生產(chǎn)級別代碼,測試簡單寫一下 } @PostMapping("/resume") @ResponseBody public String resume(String jobName) throws SchedulerException { QuartzUtils.resumeScheduleJob(scheduler,jobName); return "恢復(fù)定時(shí)任務(wù)成功:" + jobName; } @PostMapping("/update") @ResponseBody public String update(@RequestBody QuartzBean quartzBean) throws SchedulerException { QuartzUtils.updateScheduleJob(scheduler,quartzBean); return "更新定時(shí)任務(wù)調(diào)度信息成功"; }}
分布式任務(wù)調(diào)度框架—xxl-job
官網(wǎng)文檔
全新版XXL-JOB分布式定時(shí)框架SrpingBoot-XXL-JOB