一. 定時任務實現方式
定時任務實現方式:
- Java自帶的java.util.Timer類,這個類允許你調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行。一般用的較少,這篇文章將不做詳細介紹。
- 使用Quartz,這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行,配置起來稍顯復雜,有空介紹。
- 使用Spring的@Scheduled注解配合@EnableScheduling一起使用。
- SpringBoot自帶的Scheduled,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多,本文主要介紹。
說明:@Scheduled 注解用於標注這個方法是一個定時任務的方法,有多種配置可選。cron支持cron表達式,指定任務在特定時間執行;fixedRate以特定頻率執行任務;fixedRateString以string的形式配置執行頻率。
定時任務執行方式:
- 單線程(串行)
- 多線程(並行)
二. 創建定時任務
2.1、Spring boot中串行調度
在Spring boot中自帶了Seheduled,實現起來很方便,只需要在需要調度的方法上增加注解即可。如下:
package com.dxz.demo.schedule; import java.time.LocalDateTime; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @Service //@EnableScheduling 可以省略 public class PrintJob { @Scheduled(initialDelay=3000, fixedDelay = 10000) public void print() { Thread current = Thread.currentThread(); System.out.println(LocalDateTime.now() + " thread-name:" + current.getName() + ": 60 print"); } }
結果:
2.2、Spring boot中並行調度
繼承SchedulingConfigurer類並重寫其方法即可,如下:
package com.dxz.demo.schedule; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; @Configuration @EnableScheduling public class ScheduleConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(taskExecutor()); } @Bean(destroyMethod = "shutdown") public Executor taskExecutor() { return Executors.newScheduledThreadPool(10); } }
結果:
多個線程依次按照固定頻率來執行調度的。
2.3、Spring boot中異步並行調度
2.4、Spring並行調度
1.新建一個web工程,引入spring相關包,pom文件如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.dxz.demo</groupId> <artifactId>SpringDemo</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringDemo Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.6.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>SpringDemo</finalName> </build> </project>
spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <import resource="applicationContext-task.xml" /> <context:component-scan base-package="com.dxz.demo" /> </beans> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <task:annotation-driven executor="taskExecutor" scheduler="taskScheduler" /> <task:executor id="taskExecutor" pool-size="5" /> <task:scheduler id="taskScheduler" pool-size="10" /> </beans>
web.xml配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>appversion</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
調度任務類
package com.dxz.demo; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class HelloWorldTask { @Scheduled(fixedRate = 5000) public void doSomething() throws Exception { System.out.println(Thread.currentThread().getName() + " doSomething is invoked!"); } @Scheduled(fixedDelay = 5000) public void doSomething2() throws Exception { System.out.println(Thread.currentThread().getName() + " doSomething2 is invoked!"); } }
工程結構如下
結果:
從上圖中可以看到時多個線程在執行調度任務的。
三. @Scheduled參數說明:
initial-delay : 表示第一次運行前需要延遲的時間,單位是毫秒
fixed-delay : 表示從上一個任務完成到下一個任務開始的間隔, 單位是毫秒。
fixed-rate : 表示從上一個任務開始到下一個任務開始的間隔, 單位是毫秒。(如果上一個任務執行超時,則可能是上一個任務執行完成后立即啟動下一個任務)
cron : cron 表達式。(定時執行,如果上一次任務執行超時而導致某個定時間隔不能執行,則會順延下一個定時間隔時間。下一個任務和上一個任務的間隔時間不固定)
區別見圖:
4、局限性——@Scheduled的cron無法指定執行的年份
即我們假如使用下面的定時任務
@Scheduled(cron = "0 18 10 * * ? 2016-2016") public void testTaskWithDate() { logger.info("測試2016.定時任務"); }
將會報下面的錯誤
Cron expression must consist of 6 fields (found 7 in "0 18 10 * * ? 2016-2016")
Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'testTaskWithDate': Cron expression must consist of 6 fields (found 7 in "0 18 10 * * ? 2016-2016") at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled(ScheduledAnnotationBeanPostProcessor.java:405) at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.postProcessAfterInitialization(ScheduledAnnotationBeanPostProcessor.java:258) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
錯誤原因:
/** * Parse the given pattern expression. */ private void parse(String expression) throws IllegalArgumentException { String[] fields = StringUtils.tokenizeToStringArray(expression, " "); if (fields.length != 6) { throw new IllegalArgumentException(String.format("" + "cron expression must consist of 6 fields (found %d in %s)", fields.length, expression)); }
spring taks 不支持年位定時,它畢竟不是quartz,只是簡單的定時框架,比起jdk Timer就加入了線程池而以.
但是制定到年份,會存在一個問題,就是在你在過了這個時間后再啟動項目的時候,他會一直報一個memory leak的錯誤,大概的意思是你的定時任務將永遠不會被執行,導致項目一直啟動不了。
源碼里的注釋:
*The pattern is a list of six single space-separated fields: representing
* second, minute, hour, day, month, weekday. Month and weekday names can be
* given as the first three letters of the English names.
四、Cron表達式的詳細用法
字段 允許值 允許的特殊字符
秒 0-59 , - * /
分 0-59 , - * /
小時 0-23 , - * /
日期 1-31 , - * ? / L W C
月份 1-12 或者 JAN-DEC , - * /
星期 1-7 或者 SUN-SAT , - * ? / L C #
年(可選) 留空, 1970-2099 , - * /
例子:
0/5 * * * * ? : 每5秒執行一次
“*”字符被用來指定所有的值。如:"*"在分鍾的字段域里表示“每分鍾”。
“?”字符只在日期域和星期域中使用。它被用來指定“非明確的值”。當你需要通過在這兩個域中的一個來指定一些東西的時候,它是有用的。看下面的例子你就會明白。
月份中的日期和星期中的日期這兩個元素時互斥的一起應該通過設置一個問號來表明不想設置那個字段。
“-”字符被用來指定一個范圍。如:“10-12”在小時域意味着“10點、11點、12點”。
“,”字符被用來指定另外的值。如:“MON,WED,FRI”在星期域里表示”星期一、星期三、星期五”。
“/”字符用於指定增量。如:“0/15”在秒域意思是每分鍾的0,15,30和45秒。“5/15”在分鍾域表示每小時的5,20,35和50。 符號“*”在“/”前面(如:*/10)等價於0在“/”前面(如:0/10)。記住一條本質:表達式的每個數值域都是一個有最大值和最小值的集合,如: 秒域和分鍾域的集合是0-59,日期域是1-31,月份域是1-12。字符“/”可以幫助你在每個字符域中取相應的數值。如:“7/6”在月份域的時候只 有當7月的時候才會觸發,並不是表示每個6月。
L是‘last’的省略寫法可以表示day-of-month和day-of-week域,但在兩個字段中的意思不同,例如day-of- month域中表示一個月的最后一天。如果在day-of-week域表示‘7’或者‘SAT’,如果在day-of-week域中前面加上數字,它表示 一個月的最后幾天,例如‘6L’就表示一個月的最后一個星期五。
字符“W”只允許日期域出現。這個字符用於指定日期的最近工作日。例如:如果你在日期域中寫 “15W”,表示:這個月15號最近的工作日。所以,如果15號是周六,則任務會在14號觸發。如果15好是周日,則任務會在周一也就是16號觸發。如果 是在日期域填寫“1W”即使1號是周六,那么任務也只會在下周一,也就是3號觸發,“W”字符指定的最近工作日是不能夠跨月份的。字符“W”只能配合一個 單獨的數值使用,不能夠是一個數字段,如:1-15W是錯誤的。
“L”和“W”可以在日期域中聯合使用,LW表示這個月最后一周的工作日。
字符“#”只允許在星期域中出現。這個字符用於指定本月的某某天。例如:“6#3”表示本月第三周的星期五(6表示星期五,3表示第三周)。“2#1”表示本月第一周的星期一。“4#5”表示第五周的星期三。
字符“C”允許在日期域和星期域出現。這個字符依靠一個指定的“日歷”。也就是說這個表達式的值依賴於相關的“日歷”的計算結果,如果沒有“日歷” 關聯,則等價於所有包含的“日歷”。如:日期域是“5C”表示關聯“日歷”中第一天,或者這個月開始的第一天的后5天。星期域是“1C”表示關聯“日歷” 中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)。
五、表達式舉例
"0 0 12 * * ?" 每天中午12點觸發
"0 15 10 ? * *" 每天上午10:15觸發
"0 15 10 * * ?" 每天上午10:15觸發
"0 15 10 * * ? *" 每天上午10:15觸發
"0 15 10 * * ? 2005" 2005年的每天上午10:15觸發
"0 * 14 * * ?" 在每天下午2點到下午2:59期間的每1分鍾觸發
"0 0/5 14 * * ?" 在每天下午2點到下午2:55期間的每5分鍾觸發
"0 0/5 14,18 * * ?" 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鍾觸發
"0 0-5 14 * * ?" 在每天下午2點到下午2:05期間的每1分鍾觸發
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44觸發
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15觸發
"0 15 10 15 * ?" 每月15日上午10:15觸發
"0 15 10 L * ?" 每月最后一日的上午10:15觸發
"0 15 10 ? * 6L" 每月的最后一個星期五上午10:15觸發
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一個星期五上午10:15觸發
"0 15 10 ? * 6#3" 每月的第三個星期五上午10:15觸發
常用示例:
0 0 12 * * ? | 每天12點觸發 |
0 15 10 ? * * | 每天10點15分觸發 |
0 15 10 * * ? | 每天10點15分觸發 |
0 15 10 * * ? * | 每天10點15分觸發 |
0 15 10 * * ? 2005 | 2005年每天10點15分觸發 |
0 * 14 * * ? | 每天下午的 2點到2點59分每分觸發 |
0 0/5 14 * * ? | 每天下午的 2點到2點59分(整點開始,每隔5分觸發) |
0 0/5 14,18 * * ? | 每天下午的 2點到2點59分、18點到18點59分(整點開始,每隔5分觸發) |
0 0-5 14 * * ? | 每天下午的 2點到2點05分每分觸發 |
0 10,44 14 ? 3 WED | 3月分每周三下午的 2點10分和2點44分觸發 |
0 15 10 ? * MON-FRI | 從周一到周五每天上午的10點15分觸發 |
0 15 10 15 * ? | 每月15號上午10點15分觸發 |
0 15 10 L * ? | 每月最后一天的10點15分觸發 |
0 15 10 ? * 6L | 每月最后一周的星期五的10點15分觸發 |
0 15 10 ? * 6L 2002-2005 | 從2002年到2005年每月最后一周的星期五的10點15分觸發 |
0 15 10 ? * 6#3 | 每月的第三周的星期五開始觸發 |
0 0 12 1/5 * ? | 每月的第一個中午開始每隔5天觸發一次 |
0 11 11 11 11 ? | 每年的11月11號 11點11分觸發(光棍節) |
參考:http://wujq4java.iteye.com/blog/2067214
參考:http://blog.csdn.net/applebomb/article/details/52400154