上篇博客我們詳細的聊了Spring中的事件的發送和監聽,也就是常說的廣播或者通知一類的東西,詳情請移步於《JavaEE開發之Spring中的事件發送與監聽以及使用@Profile進行環境切換》。本篇博客我們就聊一下Spring中的並發編程,看一下Spring中的多線程編程和任務的定時執行。下方我們就來聊一下這兩方面的內容。
一、Spring中的多線程
本部分就來看一下Spring框架封裝下的多線程編程。因為畢竟是被Spring封裝過的異步並發編程,所以用起來還是蠻簡單的。主要還是ThreadPoolTaskExecutor的使用。下方會給出Spring框架中多線程編程的小示例。
1、創建異步執行任務的Service
下方的AsyncTaskService類就是我們創建的可支持異步任務執行的Service。主要使用了@Async注解來聲明方法,使其支持異步任務的執行。在AsyncTaskService方法中,將當前線程的ID進行了打印,以便於我們進行觀察。具體如下所示:
2、配置類中的異步設置
我們需要在Spring的配置類中進行異步的相關配置,然后我們使用@Async注解的方法才支持異步執行。下方就是相關的異步配置,首先使用@EnableAsync注解開啟異步任務支持,然后實現AsyncConfigurer接口即可。下方TaskExecutorConfig配置類中的兩個方法就是AsyncConfigurer接口的方法。
在該接口中,getAsyncExecutor()方法負責提供ThreadPoolTaskExecutor類的對象,在該方法中,我們實例化了ThreadPoolTaskExecutor類對象,然后對其進行了相應的配置。corePoolSize的值說明可開啟核心線程數,稍后會進行演示。而maxPoolSize的值是可開啟的最大線程數,queueCapacity的屬性表示每個線程中可容納的任務數。
而下方的getAsyncUncaughtExceptionHandler()方法是負責提供異常錯誤的句柄的。我們可以創建一個繼承自ErrorHandler類的錯誤處理句柄類,在這個類中重寫handleUncaughtException()方法,之后在該方法中返回該錯誤處理句柄的對象即可。當有異常時,會執行我們創建的這個錯誤句柄中相應的handleUncaughtException()方法。下方我們沒有給出錯誤處理的句柄,直接就返回null即可。
我們可以看一下ThreadPoolTaskExecutor類中的屬性,下方是這些屬性的默認值了。
3.創建測試的Main方法
下方就是我們創建用來測試的Main方法,其中從Spring容器中獲取AsyncTaskService的對象,然后在For循環中調用該對象的異步方法即可。具體代碼如下所示:
4.運行結果
我們分別給ThreadPoolTaskExecutor對象的corePoolSize和queueCapacity屬性設置相應的值,然后看起運行結果。
(1) corePoolSize = 10 && queueCapacity = 10(並行隊列的異步執行)
下方是我們將開啟線程數和線程隊列容量都設置為10的運行結果。從下方我們可以看出,開啟了10個線程,然后進行的異步執行。類似於iOS開發中GCD的並行隊列的異步執行方式。
(2) corePoolSize = 1 && queueCapacity = 10(串行隊列的異步執行)
接着我們將開啟線程的最大值設置為1,然后將每個線程隊列的容量設置為10。下方是其運行輸出結果,我們可以看出只開啟了一個新的線程來順序執行這for循環中的10次任務都會在這個線程隊列中排隊執行。這也就是串行隊列的異步執行了。
二、任務定時器
接下來我們就來看一下Spring框架中是如何使用@Schedule注解來實現任務定時執行的。@Scheduled注解中,有一些參數,我們可以為這些參數提供不同值來指定不同類型的Schedule。在@Scheduled任務定時器中,我們常用的屬性有fixedRate、fixedDelay, cron這三個屬性。下方我們將分別討論着三個屬性的具體用法,特別是cron屬性,功能是比較強大的。廢話少說,進入本部分的主題。
1、開啟Schedule支持
首先我們得在Java配置類中開啟Schedule的支持,也就是在配置類中添加上@EnableScheduling注解。具體如下所示。配置完后,我們就可以在我們的Service類中使用@Schedule注解來創建定時任務了。
2、創建定時執行的任務測試方法
接下來,我們就來創建Service類中的定時任務執行的測試方法。dateFormat屬性負責日期的格式化,sleepTimes數組中的數字則代表每次執行任務所休眠的時間,用來模擬每次任務執行所需要的時間。使用index來標記當前執行的任務次數。
每調用一次testCase()方法,任務就執行一次。testCase()方法中的代碼比較簡單,在此就不做過多贅述了。
3、fixRate = 3000
fixRate = 3000表示兩個相鄰任務的開始執行時間的間隔必須大於等於3000毫秒。下方代碼片段,是將fixeRate()方法使用@Scheduled聲明為定時任務。在fixedRate()方法中調用了this.testCase()方法。在@Scheduled注解中,我們為fixedRate屬性指定了一個值為3000ms, 也就是3秒的時間。下方我們會根據運行結果,來看一下fixedRate = 3000的具體作用。
下方截圖,就是上述代碼運行的測試結果,也就是fixedRate = 3000時的運行結果。然后我們也根據這個結果畫出啊了一個任務執行的時間軸。 第一個任務執行開始到結束使用了1秒鍾的時間,因為我們設定任務執行的固定頻率是3秒,所以下次任務要經過兩秒后才能執行。也就是說fixedRate = 3000,意味着從上一個任務執行開始,到下一個任務開始執行的間隔必須大於等於3秒,如果上一個任務的執行時間大於等於3秒的話,那么該任務執行完畢后,就緊接着執行下個任務。
4、fixedDelay = 3000
下方代碼段是將fixedDelay屬性設置成3000ms,表示在上次任務執行完成之后間隔3秒后在執行下一次任務。
下方就是上述代碼所輸出的結果,從下方結果中我們不難看出,上個任務結束的時間與下個任務開始的間隔為3秒。具體結果如下所示:
5、cron="0/3 * * * * ?"
cron屬性后邊緊跟着的是一個表達式,該表達式可表示特定的時間以及某些時間段,當系統時間到達我們設定的時間或者時間段后就會執行我們所指定的任務。在下方代碼片段中,我們將cron的值設置為"0/3 * * * * ?"。該表達式的第一個參數就代表着秒,后邊的參數表示任意。0/3表示從秒開始每3秒執行一次。
下方就是上述代碼的運行結果,從下方結果中我們可以看出,從上一個任務的結束,到下一個任務的開始並不是中間隔着3秒的時間。而是本次任務結束后,如果下次任務開始執行的時間是3秒的倍數,那么下個任務就開始執行。如果不是,就繼續等待。所以,我們不難看出下方任務開始的時間都是3的倍數。
6、cron的參數表達式
上一小節只是給出了cron參數的一種形式,接下來我們將詳細的看一下cron的參數表達式的構建規則。下方是cron表達式每個位置所表示的時間值,以及取值范圍。
-
秒:表達式的第一位是秒,允許值是0-59, "1,3,5"表示第1、3、5秒執行一次任務, “12-15”相當於12,13,14,15,表示這個范圍內的秒數每秒執行一次。“*”就是同配符了,表示任意秒數。“3/5”表示從第三秒開始,每5秒執行一次。 (, - * /)
-
分鍾:分鍾可支持的表達式形式與秒數一致,可以是“0-59”,“23,45,59”,“3/8”,“*”等格式。 (, - * /)
-
小時:與分鍾和秒的差不多,其允許的范圍是0-23,可以使用“, - * /”。 (, - * /)
-
日期:日期的范圍是1-31,可以表示為 “1-5”,“1,4,5”, “*”。“?”表示無意義的值,類似於“*”號。“2/3”,“L”也就是Last的縮寫表示該月的最后一天。“W”就是Work的縮寫,用法為“18W”表示離18號最近的工作日,比如18號是周日,那么里18號最近的工作日就是下周一了,那也就是19號。如果18號是周六,那么離18號最近的工作日就是本周的周五,也就是17號。如果18號就是周一~周五的某一天,因為18號當天就是工作日,所以“18W”就表示18號就是18號。 (? , - * / L W C)
-
月份:月份的范圍是在1~12個月,其中可以使用 (, - * /)。
-
星期:星期的范圍是1-7,1表示周日,7表示周六。可以使用 (? , - * / L C #)。#號只能在星期中使用,2#3則表示當月第三個個星期的星期一。
-
年(可選): 范圍為1970-2099,可以使用 (, - * /)。
看完上面,我們可以給出一個綜合的實例,比如“2,6-8 3/5 * LW 3 ? *”則表示離每年的3月份最后一天最近的工作日中每小時的從第三分鍾開始每隔5分鍾的第2秒以及6~8秒執行一次任務。其實這種表達信息的方式就類似於正則表達式,也就是火星文。cron的用法還是比較靈活的,而且是比較強大的。
本篇博客就先到這兒吧,下篇博客我們會繼續聊Spring的相關內容。
github源碼分享地址:https://github.com/lizelu/SpringDemo