Java定时任务
定时任务
Timer
JDK
自带的Timer
和TimerTask
可用于创建定时任务, 其中TimerTask继承了Runnable接口, 重写runnable接口就行. 观察源码可知, 构造函数中启动了一个线程, 执行一个while(true)循环, 不断从任务队列中取出任务执行, 但队列为空就退出.
关键方法
-
schedule(TimerTask task,long delay):
延迟delay毫秒执行一次task (执行完程序不结束) -
schedule(TimerTask task, Date time):
在指定时间执行指定的任务。(只执行一次) -
schedule(TimerTask task,long delay,long period):
延迟delay毫秒之后,以指定的间隔period毫秒重复执行指定的任务 -
schedule(TimerTask task, Date firstTime , long period):
在指定的时间开始按照指定的间隔(period)重复执行指定的任务 -
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
在指定的时间开始进行重复的固定速率执行任务 -
scheduleAtFixedRate(TimerTask task,long delay,long period);
在指定的延迟后开始进行重复的固定速率执行任务 -
cancel():
终止计时器, 清空任务队列(终止所有任务) -
purge():
移除队列中cancel态TimerTask
(并不会终止任务)
schedule
其中schedule(TimerTask task,long delay,long period)
比较常用, 下面测试两种场景, 先创建一个大约执行3s的任务
private static TimerTask getTask() {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("current time " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("task is over");
}
};
return timerTask;
}
任务耗时costTime比period
短
main方法中调用如下方法, 发现间隔时间为period
public static void timerTask1() {
TimerTask timerTask = getTask();
Timer timer = new Timer();
System.out.println("start time " + new Date());
timer.schedule(timerTask, 0, 5000L);
}
------------------------------------
start time Sun Mar 26 19:45:11 CST 2023
current time Sun Mar 26 19:45:11 CST 2023
task is over
current time Sun Mar 26 19:45:16 CST 2023
task is over
任务耗时costTime比period
长
main方法中调用如下方法, 发现间隔时间为costTime
public static void timerTask1() {
TimerTask timerTask = getTask();
Timer timer = new Timer();
System.out.println("start time " + new Date());
timer.schedule(timerTask, 0, 2000L);
}
--------------------------------
start time Sun Mar 26 19:48:50 CST 2023
current time Sun Mar 26 19:48:50 CST 2023
task is over
current time Sun Mar 26 19:48:53 CST 2023
task is over
所以schedule的执行策略保证将任务执行完毕, 再执行下一趟, 两次任务间隔之间多久并不能保证
scheduleAtFixedRate
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
和schedule做完全一样的测试
任务耗时costTime比period
短
main方法中调用如下方法, 发现间隔时间为period
public static void timerTask2() {
TimerTask timerTask = getTask();
getTask();
Timer timer = new Timer();
timer.scheduleAtFixedRate(timerTask, 2, 5000L);
}
-------------------------------------
current time Sun Mar 26 19:54:33 CST 2023
task is over
current time Sun Mar 26 19:54:38 CST 2023
task is over
任务耗时costTime比period
长
main方法中调用如下方法, 发现间隔时间为costTime
public static void timerTask2() {
TimerTask timerTask = getTask();
getTask();
Timer timer = new Timer();
timer.scheduleAtFixedRate(timerTask, 2, 2000L);
}
-----------------------------------------
current time Sun Mar 26 19:57:56 CST 2023
task is over
current time Sun Mar 26 19:57:59 CST 2023
task is over
schedule和scheduleAtFixedRate区别
上面测试, 发现二者效果完全一样啊, 那么为啥有两种功能类似的方法呢? 差别在于, 如果第二个参数为Date, 指定开始执行的时间在当前系统运行时间之前,scheduleAtFixedRate会把已经过去的时间也作为周期执行,而schedule不会把过去的时间算上。日常用延时,固定间隔那种方式, 发现是一样的.
Timer的缺陷
Timer开启的时单线程, 若存在多个任务, 则任务执行时间有干扰.
public static void timerTask3() {
Timer timer = new Timer();
TimerTask timerTask1 = new TimerTask() {
@Override
public void run() {
System.out.println("task1 current time " + new Date());
}
};
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
System.out.println("task2 current time " + new Date());
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
timer.scheduleAtFixedRate(timerTask1, 0, 3000L);
timer.scheduleAtFixedRate(timerTask2, 0, 2000L);
}
==================================
task1 current time Sun Mar 26 20:26:44 CST 2023
task2 current time Sun Mar 26 20:26:44 CST 2023
task2 current time Sun Mar 26 20:26:49 CST 2023
task1 current time Sun Mar 26 20:26:54 CST 2023
task2 current time Sun Mar 26 20:26:54 CST 2023
任务2执行耗时比较久, 导致任务间隔时间不符合预期
终止任务
由于是单线程, 所以Timer适合执行单任务, 想要终止任务, 直接调用cancel
即可. 在实际开发中, 有的人通过map记录TimerTask, 想关闭任务的时候从map移除对应的key, 但这种方式是只是将TimerTask从map中删除, 并没有关闭定时任务.
ScheduledExecutorService
ScheduledExecutorService是Java中另一个用于创建定时任务的类。可以使用它创建和执行定期执行的任务,并且可以控制任务的执行频率。若多个任务, 可开启多个线程执行.
scheduleAtFixedRate
任务耗时costTime比period
短
间隔时间为period
private static void scheduledExecutorServiceTask() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
System.out.println("current time " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello timer");
};
scheduledExecutorService.scheduleAtFixedRate(task, 0, 5, TimeUnit.SECONDS);
}
=====================================
current time Sun Mar 26 20:15:09 CST 2023
hello timer
current time Sun Mar 26 20:15:14 CST 2023
hello timer
任务耗时costTime比period
长
把period改为2s, 间隔时间为costTime
current time Sun Mar 26 20:17:15 CST 2023
hello timer
current time Sun Mar 26 20:17:18 CST 2023
hello timer
scheduleWithFixedDelay
这个注重的是间隔时间, 即任务执行完毕开始计时, 等待delay=2再开始下一次执行.
private static void scheduledExecutorServiceTask() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
System.out.println("start time " + new Date());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end time " + new Date());
};
scheduledExecutorService.scheduleWithFixedDelay(task, 0, 2, TimeUnit.SECONDS);
}
================================
start time Sun Mar 26 20:34:03 CST 2023
end time Sun Mar 26 20:34:06 CST 2023
start time Sun Mar 26 20:34:08 CST 2023
end time Sun Mar 26 20:34:11 CST 2023
start time Sun Mar 26 20:34:13 CST 2023
终止任务
终止所有任务调用shutdown关闭线程池就行, 但是想根据条件关闭单个任务咋办
public static void test2() throws InterruptedException {
final String jobID1 = "my_job_1";
final String jobID2 = "my_job_2";
AtomicBoolean isCanceled1 = new AtomicBoolean(false);
AtomicBoolean isCanceled2 = new AtomicBoolean(false);
final Map<String, Future> taskMap = new HashMap<>();
Future future1 = scheduler.scheduleWithFixedDelay(() -> {
System.out.println("+++++++jobID1");
if (isCanceled1.get()) {
Future future = taskMap.get(jobID1);
if (future != null) {
System.out.println("task1 is shutdown");
future.cancel(true);
}
}
}, 0, 1, TimeUnit.SECONDS);
Future future2 = scheduler.scheduleWithFixedDelay(() -> {
System.out.println("=====jobID2 " + new Date());
if (isCanceled2.get()) {
Future future = taskMap.get(jobID2);
if (future != null) {
System.out.println("task2 is shutdown");
future.cancel(true);
}
}
}
, 0, 1, TimeUnit.SECONDS);
taskMap.put(jobID1, future1);
taskMap.put(jobID2, future2);
Thread.sleep(5000L);
isCanceled1.set(true);
Thread.sleep(2000L);
isCanceled2.set(true);
Thread.sleep(1000L); // 留点时间让任务2自己关闭
if (isCanceled1.get() && isCanceled2.get()) {
System.out.println("pool is shutdown");
scheduler.shutdown();
}
}
通过以上方式可以终止单个任务, 当没有任务执行,可以将线程池关闭
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:29 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:30 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:31 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:32 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:33 CST 2023
+++++++jobID1
task1 is shutdown
=====jobID2 Sun Mar 26 21:02:34 CST 2023
=====jobID2 Sun Mar 26 21:02:35 CST 2023
=====jobID2 Sun Mar 26 21:02:36 CST 2023
task2 is shutdown
pool is shutdown
进程已结束,退出代码0
Spring task的@schedule
每隔1s执行一次.
public class ScheduledAnnotationExample {
@Scheduled(fixedRate = 1000)
public void printMessage() {
System.out.println("hello timer");
}
}
上面几种方式都会将任务执行完毕, 而不会因为任务时间耗时长就提前调度下一个任务. 还有其他几种方法 开发应该知道的定时任务——Java定时任务 - 掘金 (juejin.cn)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构