文章目录
1. Timer
Timer需要和TimerTask配合使用,才能完成调度功能。Timer表示调度器,TimerTask表示调度器执行的任务。
实现的方式为单线程,因此从JDK1.3发布之后就一直存在一些问题,大致如下:
- 多个任务之间会相互影响
- 多个任务的执行是串行的,性能较低
1.1 一次性调度
延迟一秒后执行一次
public static void main (String[] args) {
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println(dateFormat.format(new Date()));
}
};
timer.schedule(timerTask, 1000);
}
1.2 循环调度
scheduledExecutionTime() 返回最近的计划执行时间实际执行此任务。如果调用此方法在执行任务时,返回值是计划的正在进行的任务执行的执行时间。
schedule()
固定时间的调度方式,延迟一秒后,再每隔两秒执行一次
public static void main (String[] args) {
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("scheduledExecutionTime: " + dateFormat.format(scheduledExecutionTime()));
System.out.println("Date: " + dateFormat.format(new Date()));
System.out.println();
}
};
timer.schedule(timerTask, 1000, 2000);
}
当执行时间超过间隔时间,任务的下一次执行时间是相对于上一次实际执行完成的时间点 ,因此执行时间会不断延后。
输出:
scheduledExecutionTime: 14:19:40
Date: 14:19:43
scheduledExecutionTime: 14:19:43
Date: 14:19:46
scheduledExecutionTime: 14:19:46
Date: 14:19:49
scheduleAtFixedRate()
固定频率的调度方式,延迟一秒,再每隔两秒执行一次
public static void main (String[] args) {
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("scheduledExecutionTime: " + dateFormat.format(scheduledExecutionTime()));
System.out.println("Date: " + dateFormat.format(new Date()));
System.out.println();
}
};
timer.scheduleAtFixedRate(timerTask, 1000, 2000);
}
当执行时间超过间隔时间,任务的下一次执行时间是相对于上一次开始执行的时间点 ,因此执行时间不会延后。
输出:
scheduledExecutionTime: 14:20:49
Date: 14:20:52
cheduledExecutionTime: 14:20:51
Date: 14:20:55
scheduledExecutionTime: 14:20:53
Date: 14:20:58
2. ScheduledExecutorService
ScheduledExecutorService在设计之初就是为了解决Timer&TimerTask的这些问题。因为天生就是基于多线程机制,所以任务之间不会相互影响(只要线程数足够。当线程数不足时,有些任务会复用同一个线程)。
除此之外,因为其内部使用的延迟队列,本身就是基于等待/唤醒机制实现的,所以CPU并不会一直繁忙。同时,多线程带来的CPU资源复用也能极大地提升性能。
2.1 一次性调度
schedule()方法有很多重载,可以传Runnable或Callable接口。
Runnable无返回值,延迟一秒后执行
private void schedule() throws InterruptedException, ExecutionException {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.schedule(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("schedule: " + dateFormat.format(new Date()));
}, 1000, TimeUnit.MILLISECONDS);
System.out.println(dateFormat.format(new Date()));
}
Callable有返回值,延迟一秒后执行
private void scheduleCallable() throws Exception {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<String> future = service.schedule(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("schedule: " + dateFormat.format(new Date()));
return "success";
}, 1000, TimeUnit.MILLISECONDS);
System.out.println("return: " + future.get() + " " + dateFormat.format(new Date()));
}
2.2 循环调度
scheduleAtFixedRate()
固定频率,延迟一秒,然后每两秒执行一次
相对于任务执行的开始时间
public static void main (String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
service.scheduleAtFixedRate(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("scheduleAtFixedRate: " + dateFormat.format(new Date()));
}, 1000, 2000, TimeUnit.MILLISECONDS);
System.out.println(dateFormat.format(new Date()));
}
输出:
15:20:09
scheduleAtFixedRate: 15:20:13
scheduleAtFixedRate: 15:20:16
scheduleAtFixedRate: 15:20:19
scheduleWithFixedDelay()
固定延迟,延迟一秒,然后每两秒执行一次
相对于任务执行的完成时间
public static void main (String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
service.scheduleWithFixedDelay(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("scheduleWithFixedDelay: " + dateFormat.format(new Date()));
}, 1000, 2000, TimeUnit.MILLISECONDS);
System.out.println(dateFormat.format(new Date()));
}
输出:
15:21:09
scheduleWithFixedDelay: 15:21:13
scheduleWithFixedDelay: 15:21:18
scheduleWithFixedDelay: 15:21:23
3. Spring Task
Spring Task包含在spring-boot-starter基础模块中了,所有不需要增加额外的依赖。使用的时候需要在main方法上添加注解 @EnableScheduling
,通过@Scheduled
注解使用。
public @interface Scheduled {
String CRON_DISABLED = "-";
// 使用cron表达式
String cron() default "";
// 时区
String zone() default "";
// 固定延迟,每次执行任务之后间隔多久再次执行该任务。
long fixedDelay() default -1L;
String fixedDelayString() default "";
// 固定频率,每隔多少时间就启动任务,不管该任务是否启动完成。
long fixedRate() default -1L;
String fixedRateString() default "";
//初次执行任务之前需要等待的时间
long initialDelay() default -1L;
String initialDelayString() default "";
}
使用
@Component
public class ScheduledTest {
private static final Logger log = LoggerFactory.getLogger(ScheduledTest.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
// 固定频率
@Scheduled(fixedRate = 1000)
public void fixedRateTest() throws InterruptedException {
Thread.sleep(2000);
log.info("fixedRateTest: {}", dateFormat.format(new Date()));
}
// 固定延迟
@Scheduled(fixedDelay = 1000)
public void fixedDelayTest() throws InterruptedException {
Thread.sleep(2000);
log.info("fixedDelayTest: {}", dateFormat.format(new Date()));
}
// cron表达式时间定义规则,当方法的执行时间超过任务调度频率时,调度器会在下个周期执行。
@Scheduled(cron = "0/1 * * * * *")
public void cronTest() throws InterruptedException {
Thread.sleep(2000);
log.info("cronTest: {}", dateFormat.format(new Date()));
}
}
默认Spring配置中调度为单线程,线程数为1。当有多个定时任务的时候,效率很低,可以改为多线程,每个线程执行一个调度任务。
修改application.yml,将线程数为10
spring:
task:
scheduling:
pool:
size: 10
或
@Configuration
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
}
}
4. 总结
调度机制都可分为固定频率和固定延迟。
固定频率为相对于任务执行的开始时间,固定延迟为相对于任务执行的完成时间。
5. Cron 表达式
Cron表达式是一个字符串,包括6~7个时间元素。
Cron语法格式
Seconds Minutes Hours DayofMonth Month DayofWeek
Cron格式中每个时间元素的说明
时间元素 | 可出现的字符 | 有效数值范围 |
---|---|---|
Seconds | , - * / | 0-59 |
Minutes | , - * / | 0-59 |
Hours | , - * / | 0-23 |
DayofMonth | , - * / ? L W | 0-31 |
Month | , - * / | 1-12 |
DayofWeek | , - * / ? L # | 1-7或SUN-SAT |
Cron格式中特殊字符说明
字符 | 作用 | 举例 |
---|---|---|
, | 列出枚举值 | 在Minutes域使用5,10,表示在5分和10分各触发一次 |
- | 表示触发范围 | 在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次 |
* | 匹配任意值 | 在Minutes域使用*, 表示每分钟都会触发一次 |
/ | 起始时间开始触发,每隔固定时间触发一次 | 在Minutes域使用5/10,表示5分时触发一次,每10分钟再触发一次 |
? | 在DayofMonth和DayofWeek中,用于匹配任意值 | 在DayofMonth域使用?,表示每天都触发一次 |
# | 在DayofMonth中,确定第几个星期几 | 1#3表示第三个星期日 |
L | 表示最后 | 在DayofWeek中使用5L,表示在最后一个星期四触发 |
W | 表示有效工作日(周一到周五) | 在DayofMonth使用5W,如果5日是星期六,则将在最近的工作日4日触发一次 |
参考:
Java定时调度机制 - Timer
Java定时调度机制 - ScheduledExecutorService
Spring Scheduling Tasks Guide
Spring @Scheduled 文档
springboot集成schedule(深度理解)
spring boot 2X中@Scheduled实现定时任务及多线程配置
mall整合SpringTask实现定时任务