引言:定时任务的实现方式总结
定时任务的多种实现方式
定时任务(延时任务)
即定时完成的任务,或者说可以延迟一段时间完成的任务
使用场景有很多:
- 下订单,未支付,30min后自动取消订单
- 24小时候红包自动退回账户
- 每天凌晨自动备份
- 每个月跑一次脚本等等
最近我在实习的过程中也需要定时任务,因此就搜集资料整合了一下
实现方式
经过总结多方资料,有以下几种实现定时任务的方式:
- 死循环的普通方式
- 使用
ScheduledExecutorService
- 使用
DelayQueue
- 使用Spring的定时任务注解
- 使用Redis
- 使用定时任务框架
- 外部调用
【法一】死循环的方式
死循环的方式:
使用一个Map来保存任务信息,死循环反复遍历这个Map(比如使用迭代器遍历)
每次循环记录当前的毫秒值,如果当前毫秒值大于等于我设定的值,那么就去执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public void wayToImplementScheduled() { Map<String, Long> taskMap = new HashMap<>(); taskMap.put("任务1", Instant.now().plusSeconds(10).toEpochMilli()); while (true) { Set<String> taskKey = taskMap.keySet(); Iterator<String> it = taskKey.iterator(); while (it.hasNext()) { String key = it.next(); if (taskMap.get(key) <= Instant.now().toEpochMilli()) { it.remove(); System.out.println("执行" + key); } } } }
|
注意:Instant
是Java8提供的新的类,常见的用法如下:
1 2 3 4 5 6
| Instant instant = Instant.now();
Instant instant = Instant.ofEpochMilli(new Date().getTime());
Instant instant = Instant.parse("1995-10-23T10:12:35Z");
|
【法二】使用ScheduledExecutorService
使用一个ScheduledExecutorService
实现
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public void wayToImplementScheduled() { ScheduledExecutorService ses = Executors.newScheduledThreadPool(3); ses.scheduleWithFixedDelay( ()->{ System.out.println("执行任务"); }, 1, 10, TimeUnit.SECONDS ); }
|
【法三】使用DelayQueue
注意DelayQueue
的使用:
- 泛型需要实现
Delayed
接口:这会导致需要重写两个方法
getDelay()
:返回剩余的延迟时间
compareTo()
:表明此队列的排序方式
- 此处的示例构造函数还传入了
Runnable
接口
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 45 46
| class DelayTask implements Delayed { long delay; Runnable task;
public DelayTask(long delay, Runnable task) { this.delay = Instant.now().toEpochMilli() + delay; this.task = task; }
@Override public long getDelay(TimeUnit unit) { return unit.convert(delay - Instant.now().toEpochMilli(), TimeUnit.MILLISECONDS); }
@Override public int compareTo(Delayed o) { if (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS) > 0) { return 1; } else if (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS) < 0) { return -1; } else { return 0; } } }
@Override public void wayToImplementScheduled() { DelayQueue<DelayTask> delayQueue = new DelayQueue<>(); delayQueue.add(new DelayTask(5000,()->{ System.out.println("执行任务1"); })); delayQueue.add(new DelayTask(3000,()->{ System.out.println("执行任务2"); }));
while (!delayQueue.isEmpty()){ try { delayQueue.take().task.run(); } catch (InterruptedException e) { e.printStackTrace(); } } }
|
【法四】Spring的定时任务
使用Spring的自带注解:
首先给Spring的启动类加上注解@EnableScheduling
比如这样:
1 2 3 4 5 6 7
| @EnableScheduling @SpringBootApplication public class HynisApplication { public static void main(String[] args) { SpringApplication.run(HynisApplication.class, args); } }
|
然后使用@Scheduled
这个注解:
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
| @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { String CRON_DISABLED = "-";
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 "";
TimeUnit timeUnit() default TimeUnit.MILLISECONDS; }
|
看@Scheduled
注解源码可以了解到:
- 可以标记注解与方法
- 可以使用cron表达式
demo如下:
1 2 3 4 5 6
| @Override @Scheduled(cron = "*/10 * * * * *")
public void wayToImplementScheduled() { System.out.println("定时任务执行:" + LocalDateTime.now()); }
|
【法五】使用Redis
上面的任务调度方法都是单机的方式,遇到分布式场景会歇菜(除非借助第三方工具,或是加锁)
待补充….
【法六】使用定时任务框架Quartz
待补充….
【法七】外部调用
可以使用比方说外部的一个curl,调用本机的一个接口的方式来实现定时调用
curl可以是一个我们自己实现的脚本去控制实现定时任务
定时任务的实现要注意
1、如何设置一个定时任务的执行周期
注意定时任务的性能问题,避免在一个周期内,数据无法执行完,一般取50%,或者20%的时间比较合适
2、注意改方法是否适合分布式场景
参考资料