实现方式一:Timer

schedule 示例清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
// 从现在开始, 此后每隔 3 秒执行一次任务
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("execute task");
}
}, 0, 3 * 1000);
}
}

特性:任务由同一个线程调度,串行执行。下一个任务按照前一个任务的实际执行完成的时间起算向后推一个时间间隔的时长,若前一个任务发生延迟,下一个任务也会被延后执行。不存在线程安全问题。

scheduleAtFixedRate 示例清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
// 从现在开始, 此后每隔 3 秒执行一次任务
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("execute task");
}
}, 0, 3 * 1000);
}
}

特性:下一个任务按照前一个任务开始执行的时间开始计算,若前一个任务发生延迟,下一个任务不会被延后,存在并发性的可能性,需要考虑线程安全问题。

实现方式二:ScheduledExecutorService

scheduleWithFixedDelay 示例清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 从现在开始, 此后每隔 3 秒执行一次任务
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("execute task");
}
}, 0, 3, TimeUnit.SECONDS);
}
}

newSingleThreadScheduledExecutor() 等效于 Executors.newScheduledThreadPool(1)。

特性:下一个任务按照前一个任务的实际执行完成的时间起算向后推一个时间间隔的时长,若前一个任务发生延迟,下一个任务也会被延后执行。是基于不固定时间间隔进行的任务调度。

scheduleAtFixedRate 示例清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 从现在开始, 此后每隔 3 秒执行一次任务
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("execute task");
}
}, 0, 3, TimeUnit.SECONDS);
}
}

newSingleThreadScheduledExecutor() 等效于 Executors.newScheduledThreadPool(1)。

特性:下一个任务按照前一个任务开始执行的时间开始计算,若前一个任务发生延迟,下一个任务不会被延后,是基于固定时间间隔进行的任务调度。

借助 Calendar 实现复杂的任务调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 4);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
// 距凌晨 04 点的时间戳
long delay = calendar.getTime().getTime() - System.currentTimeMillis();
// 每天凌晨 04 点执行任务
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("execute task");
}
}, delay, 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS);
}
}

实现方式三:Quartz

Cron 触发器

表达式格式:秒 分 时 日 月 周 年 [ 可选 ]
字段名 值范围 特殊字符
0 - 59 ,  -  *  /
0 - 59 ,  -  *  /
0 - 23 ,  -  *  /
0 - 31 ,  -  *  /  L  W  C  ?
1 - 12 [ JAN - DEC ] ,  -  *  /
1 - 7   [ SUN - SAT ] ,  -  *  /  L  C  #  ?
1970 - 2099 ,  -  *  /

 

特殊字符     含义描述
, 用于指定多个值。如在周字段中,MON, WED, FRI 表示一三五
- 用于指定一个范围的值。如在时字段中,03-04 表示凌晨 3 至 4 点
* 表示任意值
/ 表示增量值。如在分字段中,5/30 表示从第 5 分钟起,每 30 分钟一次
# 只用于周字段,表示月的第几周,如 MON#2 表示该月的第二个星期一
? 表示不确定值,是什么值不重要。
C Calendar 的缩写,表示基于日历计算出来的值。
L Last 的缩写,在日字段中单独出现,表示该月最后一天;
在周字段中单独出现表示 7 或 SAT,组合出现,如 7L,表示该月最后一个周六
W 表示工作日(周一 至 周五)

 

示例     描述
0 0 3 * * * 每天凌晨 3 点触发
0 0 3 * * ? 每天凌晨 3 点触发
0 0 3 * * ? * 每天凌晨 3 点触发
0 * 3 * * ? 每天凌晨 3 点 至 3 点 59 分,每分钟触发一次
0 */5 * * * ? 每隔 5 分钟触发一次
0 */5 3 * * ? 每天凌晨 3 点 至 3 点 59 分,每 5 分钟触发一次
0 0 3-4 * * ? 每天凌晨 3 至 4 点,每小时触发一次
0 0 3 * * MON,WED,FRI 每个周一、周三、周五的凌晨 3 点触发一次
0 0 3 L * ? 每个月的最后一天凌晨 3 点触发
0 0 3 * * 2L 每个月的最后一个周一的凌晨 3 点触发
0 0 3 * * MON#2 每个月的第二个周一的凌晨 3 点触发

 

示例清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CronDemo {
public static void main(String[] args) throws Throwable {
// 从工厂中获取一个实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 作业实例, 名称 "MY-JOB"
JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("MY-JOB").build();
JobDataMap jobDataMap = job.getJobDataMap();
// 键值对信息
jobDataMap.put("msg", "Hello World!");
// Cron 触发器, 每 5 秒触发一次
CronTrigger trigger = TriggerBuilder.newTrigger()
.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();
scheduler.scheduleJob(job, trigger);
scheduler.start();
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
JobKey jobKey = jobDetail.getKey();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
// 任务执行时, 打印一条消息
System.out.println("Executing " + jobKey + " : " + jobDataMap.getString("msg"));
}
}
}

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
</dependencies>

log4j.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://toolkit.alibaba-inc.com/dtd/log4j/log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%n%p %d{yyyy-MM-dd HH:mm:ss} %c : %L %n%m%n%n" />
</layout>
</appender>
<logger name="org.quartz" additivity="true">
<level value="WARN" />
</logger>
<root>
<level value="DEBUG" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

与 spring 整合

整合示例清单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.fanlychie.cron;
import org.quartz.JobKey;
import org.quartz.JobDetail;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class MyJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
JobDetail jobDetail = context.getJobDetail();
JobKey jobKey = jobDetail.getKey();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
// 任务执行时, 打印一条消息
System.out.println("Executing " + jobKey + " : " + jobDataMap.getString("msg"));
}
}

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>

beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<bean id="jobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="MY-JOB" />
<property name="jobClass" value="org.fanlychie.cron.MyJob" />
<property name="jobDataAsMap">
<map>
<entry key="msg">
<value>Hello World!</value>
</entry>
</map>
</property>
<property name="durability" value="true" />
</bean>
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail" />
<property name="cronExpression" value="0/5 * * * * ?" />
</bean>
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
</bean>

log4j.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://toolkit.alibaba-inc.com/dtd/log4j/log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%n%p %d{yyyy-MM-dd HH:mm:ss} %c : %L %n%m%n%n" />
</layout>
</appender>
<logger name="org.quartz" additivity="true">
<level value="WARN" />
</logger>
<logger name="org.springframework" additivity="true">
<level value="WARN" />
</logger>
<root>
<level value="DEBUG" />
<appender-ref ref="console" />
</root>
</log4j:configuration>

测试清单

1
2
3
4
5
6
7
8
9
public class CronTest {
public static void main(String[] args) throws Throwable {
new ClassPathXmlApplicationContext("beans.xml");
}
}