定时任务

推荐

在 Java 中实现定时任务 有很多种方式,JDK提供了 Timer 类 来帮助开发者创建定时任务,另外也有很多的第三方框架提供了对定时任务的支持,比如 Spring 的 schedule 以及 著名的 quartz 等等。

JDK Timer计时器

https://www.apiref.com/java11-zh/java.base/java/util/Timer.html

线程的工具,用于在后台线程中安排将来执行的任务。 可以将任务安排为一次性执行,或者以固定间隔重复执行。

**线程安全:**多个线程可以共享单个 Timer 对象,无需外部同步。

**实现说明:**此类可扩展到大量并发计划任务(数千个应该没有问题)。 在内部,它使用二进制堆来表示其任务队列,因此调度任务的成本是O(log n),其中n是并发计划任务的数量。Timer类有一个任务队列的内部类TaskQueue,定义了一个 TimerTask 数组 queue,默认长度128,自动扩容策略:2*queue.length

实现说明:所有构造函数都创建一个新计时器,每个 Timer 对象都对应一个后台线程,用于按顺序执行所有计时器的任务

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
public class Test {
public static void main(String[] args) {
TimerTask timerTask1 = new MyTimerTask("timerTask1");
TimerTask timerTask2 = new MyTimerTask("timerTask2");
TimerTask timerTask3 = new MyTimerTask("timerTask3");
// 创建定时器
Timer timer = new Timer();
// 提交计划任务
timer.schedule(timerTask1, Date.from(LocalDateTime.now().plusSeconds(3).toInstant(ZoneOffset.of("+8"))));
timer.schedule(timerTask2, Date.from(LocalDateTime.now().plusSeconds(1).toInstant(ZoneOffset.of("+8"))));
timer.schedule(timerTask3, Date.from(LocalDateTime.now().plusSeconds(5).toInstant(ZoneOffset.of("+8"))));
}
}
class MyTimerTask extends TimerTask {
private String taskName;
public MyTimerTask(String taskName) {
this.taskName = taskName;
}

// @SneakyThrows
@Override
public void run() {
System.out.println(taskName + " 定时任务的线程ID: " + Thread.currentThread().getId());
// throw new Exception();
}
}

// 运行结果:
timerTask2 定时任务的线程ID: 13
timerTask1 定时任务的线程ID: 13
timerTask3 定时任务的线程ID: 13

注意:Timer 运行多个 TimeTask 时,倘若有一个 TimeTask 没有捕获抛出的异常,那么后面的 TimeTask 将取消运行(使用 ScheduledExecutorService 没有这个问题),同时,Timer也将取消。

TimerTask.cancel()

image-20210713210914436

Timer.cancel()

在对 Timer 对象的最后一个实时引用消失并且所有未完成的任务都已完成执行后,计时器的任务执行线程将正常终止(并成为垃圾收集的对象)。 但是,这可能需要任意长的时间才能发生。 默认情况下,任务执行线程不作为守护线程运行,因此它能够防止应用程序终止。 如果调用者想快速终止定时器的任务执行线程,调用者应该调用定时器的 cancel 方法。

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Timer timer = new Timer();
// 这里的任务线程(timer对象)将成为垃圾回收的对象,但不会立即被回收。调用者应该调用 cancel 方法。
// timer.cancel();
}
}

image-20210713203851443

1、指定时间点执行一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Timer;
import java.util.TimerTask;

public class Test {
public static void main(String[] args) {
// 创建定时器
Timer timer = new Timer();
// 提交计划任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("指定时间点执行一次");
timer.cancel();
}
}, DateUtils.parse("1970-01-01 08:00:00", DatePatterns.NORM_DATETIME_PATTERN)); // 如果时间已经过去,则将任务安排为立即执行。
}
}

2、间隔时间重复执行(轮询)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Test {
public static void main(String[] args) {
// 创建定时器
Timer timer = new Timer();
// 提交计划任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("间隔时间重复执行");
}
}, new Date(), 1000);
}
}

ScheduledThreadPoolExecutor

java.util.concurrent 包定时任务相关:

image-20210713150610278

Spring Batch 批处理框架,真心强啊!!

https://mp.weixin.qq.com/s?__biz=MzUyNDc0NjM0Nw==&mid=2247492574&idx=1&sn=cca34b269aba800e39f2f9872e53c2cb