內容目錄
Scheduling Tasks 定時任務排程器概述
專案開發中經常會用到定時任務,在實現定時任務都方式也是多種多樣。下面列舉幾種常見的定時任務實現方式:
- Quartz:Quartz的使用相當廣泛,它是一個功能強大的排程器,當然使用起來也相對麻煩。參考: [Java] quartz 工作定時排程器 – 我的工作日記
- java.util包裡的Timer,它也可以實現定時任務但是功能過於單一所有使用很少。
- Spring Boot 自帶的定時任務Schedule,其實可以把它看作是一個簡化版的,輕量級的Quartz,使用起來也相對方便很多。參考: Spring Boot Scheduling Tasks Guide
實作 Scheduling Tasks 定時任務排程器
建立 Scheduling Tasks 定時任務排程器
建立一個 Class: ScheduleTasks , 並用 @Component 注冊
@Component public class ScheduledTasks { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); @Scheduled(fixedDelay = 60000) // fixedDelay = 60000 表示當前方法執行完畢 60000ms(1分鐘) 後,Spring scheduling會再次呼叫該方法 public void testFixDelay() { logger.info("===fixedDelay: 時間:{}", dateFormat.format(new Date())); } @Scheduled(fixedRate = 60000) // fixedRate = 60000 表示當前方法開始執行 60000ms(1分鐘) 後,Spring scheduling會再次呼叫該方法 public void testFixedRate() { logger.info("===fixedRate: 時間:{}", dateFormat.format(new Date())); } @Scheduled(initialDelay = 180000, fixedRate = 5000) // initialDelay = 180000 表示延遲 180000 (3秒) 執行第一次任務, 然後每 5000ms(5 秒) 再次呼叫該方法 public void testInitialDelay() { logger.info("===initialDelay: 時間:{}", dateFormat.format(new Date())); } @Scheduled(cron = "0 0/1 * * * ?") // cron接受cron表示式,根據cron表示式確定定時規則 public void testCron() { logger.info("===cron: 時間:{}", dateFormat.format(new Date())); } }
使用@Scheduled
annotations, 來建立定時任務 這個註解用來標註一個定時任務方法。通過看@Scheduled
annotations,原始碼可以看出它支援多種引數:
- cron: cron表示式,指定任務在特定時間執行;
- fixedDelay: 表示上一次任務執行完成後多久再次執行,引數型別為long,單位ms;
- fixedDelayString: 與fixedDelay含義一樣,只是引數型別變為String;
- fixedRate: 表示按一定的頻率執行任務,引數型別為long,單位ms;
- fixedRateString: 與fixedRate的含義一樣,只是將引數型別變為String;
- initialDelay: 表示延遲多久再第一次執行任務,引數型別為long,單位ms;
- initialDelayString: 與initialDelay的含義一樣,只是將引數型別變為String;
- zone: 時區,預設為當前時區,一般沒有用到
啟用 Scheduling Tasks 定時任務排程器
要加入 @EnableScheduling 才會有作用
@SpringBootApplication @EnableScheduling public class AppsApplication { public static void main(String[] args) { SpringApplication.run(AppsApplication.class, args); } }
執行結果 (單執行緒)
由執行結果可以發現, fixedRate
速度比 fixedDelay
快執行
#執行結果 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedRate: 時間:09:45:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:09:45:25 [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 443 (https) 80 (http) with context path '' [ main] com.apps.AppsApplication : Started AppsApplication in 9.446 seconds (JVM running for 11.074) [ scheduling-1] com.apps.component.ScheduledTasks : ===cron: 時間:09:46:00 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedRate: 時間:09:46:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:09:46:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===cron: 時間:09:47:00 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedRate: 時間:09:47:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:09:47:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===cron: 時間:09:48:00 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedRate: 時間:09:48:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:09:48:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:09:48:25 [ scheduling-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:09:48:30 [ scheduling-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:09:48:35 [ scheduling-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:09:48:40 [ scheduling-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:09:48:45 [ scheduling-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:09:48:50
多執行緒處理定時任務(multiple thread scheduler)
控制台(Console)輸出的結果,所有的定時任務都是通過一個執行緒來處理的,從 Class: ScheduledAnnotationBeanPostProcessor
類開始一路找下去。果然,在ScheduledTaskRegistrar
(定時任務註冊類)中的ScheduleTasks中又這樣一段判斷:
/** * Schedule all registered tasks against the underlying * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}. */ @SuppressWarnings("deprecation") protected void scheduleTasks() { if (this.taskScheduler == null) { this.localExecutor = Executors.newSingleThreadScheduledExecutor(); this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor); } if (this.triggerTasks != null) { for (TriggerTask task : this.triggerTasks) { addScheduledTask(scheduleTriggerTask(task)); } } if (this.cronTasks != null) { for (CronTask task : this.cronTasks) { addScheduledTask(scheduleCronTask(task)); } } if (this.fixedRateTasks != null) { for (IntervalTask task : this.fixedRateTasks) { addScheduledTask(scheduleFixedRateTask(task)); } } if (this.fixedDelayTasks != null) { for (IntervalTask task : this.fixedDelayTasks) { addScheduledTask(scheduleFixedDelayTask(task)); } } }
這就說明如果 taskScheduler 為空,那麼就給定時任務做了一個單執行緒的執行緒池,正好在這個類 ScheduledTaskRegistrar
中還有一個設定taskScheduler的方法:
/** * Set the {@link TaskScheduler} to register scheduled tasks with, or a * {@link java.util.concurrent.ScheduledExecutorService} to be wrapped as a * {@code TaskScheduler}. */ public void setScheduler(@Nullable Object scheduler) { if (scheduler == null) { this.taskScheduler = null; } else if (scheduler instanceof TaskScheduler) { this.taskScheduler = (TaskScheduler) scheduler; } else if (scheduler instanceof ScheduledExecutorService) { this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler)); } else { throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass()); } }
這樣問題就很簡單了,我們只需用呼叫這個方法顯式的設定一個ScheduledExecutorService就可以達到同時執行的多執行緒效果了。我們要做的僅僅是實現SchedulingConfigurer介面,重寫configureTasks方法就OK了
/** * Description: 多執行緒執行定時任務 */ @Configuration //所有的定時任務都放在一個執行緒池中,定時任務啟動時使用不同都執行緒。 public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 設定一個長度10的定時任務執行緒池 taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); } }
執行結果 (多執行緒)
[pool-1-thread-2] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:15:41:37 [pool-1-thread-1] com.apps.component.ScheduledTasks : ===fixedRate: 時間:15:41:37 [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 443 (https) 80 (http) with context path '' [ main] com.apps.AppsApplication : Started AppsApplication in 11.321 seconds (JVM running for 12.98) [pool-1-thread-2] com.apps.component.ScheduledTasks : ===cron: 時間:15:42:00 [pool-1-thread-1] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:15:42:37 [pool-1-thread-3] com.apps.component.ScheduledTasks : ===fixedRate: 時間:15:42:37 [pool-1-thread-4] com.apps.component.ScheduledTasks : ===cron: 時間:15:43:00 [pool-1-thread-6] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:15:43:37 [pool-1-thread-5] com.apps.component.ScheduledTasks : ===fixedRate: 時間:15:43:37 [pool-1-thread-2] com.apps.component.ScheduledTasks : ===cron: 時間:15:44:00 [pool-1-thread-1] com.apps.component.ScheduledTasks : ===initialDelay: 時間:15:44:37 [pool-1-thread-7] com.apps.component.ScheduledTasks : ===fixedRate: 時間:15:44:37 [pool-1-thread-8] com.apps.component.ScheduledTasks : ===fixedDelay: 時間:15:44:37 [pool-1-thread-3] com.apps.component.ScheduledTasks : ===initialDelay: 時間:15:44:42 [pool-1-thread-4] com.apps.component.ScheduledTasks : ===initialDelay: 時間:15:44:47
Cron表示式
cron表示式定義
Cron表示式是一個字串,是由空格隔開的6或7個域組成,每一個域對應一個含義(秒 分 時 每月第幾天 月 星期 年)其中年是可選欄位。
┌───────────── second (0-59) │ ┌───────────── minute (0 - 59) │ │ ┌───────────── hour (0 - 23) │ │ │ ┌───────────── day of the month (1 - 31) │ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC) │ │ │ │ │ ┌───────────── day of the week (0 - 7) │ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN) │ │ │ │ │ │ * * * * * *
每個域可出現的字元型別和各字元的含義
秒:可出現: ”, – * /” 左列的四個字元,有效範圍為0-59的整數 分:可出現: ”, – * /” 左列的四個字元,有效範圍為0-59的整數 時:可出現: ”, – * /” 左列的四個字元,有效範圍為0-23的整數 每月第幾天:可出現: ”, – * / ? L W C” 左列的八個字元,有效範圍為0-31的整數 月:可出現: ”, – * /” 左列的四個字元,有效範圍為1-12的整數或JAN-DEc 星期:可出現: ”, – * / ? L C #” 左列的八個字元,有效範圍為1-7的整數或SUN-SAT兩個範圍。1表示星期天,2表示星期一, 依次類推
特殊字元含義
* : 表示匹配該域的任意值,比如在秒*, 就表示每秒都會觸發事件。; ? : 只能用在每月第幾天和星期兩個域。表示不指定值,當2個子表示式其中之一被指定了值以後,為了避免衝突,需要將另一個子表示式的值設為“?”; – : 表示範圍,例如在分域使用5-20,表示從5分到20分鐘每分鐘觸發一次 / : 表示起始時間開始觸發,然後每隔固定時間觸發一次,例如在分域使用5/20,則意味著5分,25分,45分,分別觸發一次. , : 表示列出列舉值。例如:在分域使用5,20,則意味著在5和20分時觸發一次。 L : 表示最後,只能出現在星期和每月第幾天域,如果在星期域使用1L,意味著在最後的一個星期日觸發。 W : 表示有效工作日(週一到週五),只能出現在每月第幾日域,系統將在離指定日期的最近的有效工作日觸發事件。注意一點,W的最近尋找不會跨過月份 LW : 這兩個字元可以連用,表示在某個月最後一個工作日,即最後一個星期五。 # : 用於確定每個月第幾個星期幾,只能出現在每月第幾天域。例如在1#3,表示某月的第三個星期日。
Macros
You can use these macros instead of the six-digit value, thus: @Scheduled(cron = "@hourly")
.
Macro | Meaning |
---|---|
| once a year ( |
| once a month ( |
| once a week ( |
| once a day ( |
| once an hour, ( |
你必須 登入 才能發表評論。