概述
1. Timer
Timer
是 JDK 自带的定时任务执行类,可以直接使用 Timer
来实现定时任务。这种实现方式比较简单,可以指定首次执行的延迟时间、首次执行的具体日期时间,以及执行频率,能满足日常需要。Timer
是线程安全的,因为背后是单线程在执行所有任务。Timer
是基于绝对时间调度的,而不是基于相对时间,所以它对系统时间的改变非常敏感。
1.1 使用:
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 测试定时任务
*/
public class Test {
public static void main(String[] args) {
// 定义一个任务
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("Run timerTask:" + new Date());
}
};
// 计时器
Timer timer = new Timer();
// 添加执行任务(延迟 1s 执行,每 2s 执行一次)
timer.schedule(timerTask, 1000, 2000);
}
}
几个重要的方法:
schedule()
:开始调度任务,提供了几个包装方法;cancle()
:终止任务调度,取消当前调度的所有任务,正在运行的任务不受影响;purge()
:从任务队列中移除所有已取消的任务;
另外,java.util.TimerTask
就是实现了 Runnable
接口,具体任务逻辑则是在 run()
方法里去实现。
public abstract class TimerTask implements Runnable {
// ......
}
执行结果如下:
Run timerTask:Wed Mar 17 10:42:05 CST 2021
Run timerTask:Wed Mar 17 10:42:07 CST 2021
Run timerTask:Wed Mar 17 10:42:09 CST 2021
Run timerTask:Wed Mar 17 10:42:11 CST 2021
Run timerTask:Wed Mar 17 10:42:13 CST 2021
Run timerTask:Wed Mar 17 10:42:15 CST 2021
Run timerTask:Wed Mar 17 10:42:17 CST 2021
Run timerTask:Wed Mar 17 10:42:19 CST 2021
Run timerTask:Wed Mar 17 10:42:21 CST 2021
......
1.2 Timer 缺点分析:
问题 1:任务执行时间长影响其他任务。
开启两个线程,如下:
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
/**
* 测试定时任务
*/
public class Test {
public static void main(String[] args) {
// 定义任务 1
TimerTask timerTask1 = new TimerTask() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);// 休眠 3 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Run timerTask 1:" + new Date());
}
};
// 定义任务 2
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
System.out.println("Run timerTask 2:" + new Date());
}
};
// 计时器
Timer timer = new Timer();
// 添加执行任务(延迟 1s 执行,每 3s 执行一次)
timer.schedule(timerTask1, 1000, 3000);
timer.schedule(timerTask2, 1000, 3000);
}
}
执行结果如下:
Run timerTask 1:Wed Mar 17 10:50:54 CST 2021
Run timerTask 2:Wed Mar 17 10:50:54 CST 2021
Run timerTask 1:Wed Mar 17 10:50:57 CST 2021
Run timerTask 2:Wed Mar 17 10:50:57 CST 2021
Run timerTask 1:Wed Mar 17 10:51:00 CST 2021
Run timerTask 1:Wed Mar 17 10:51:03 CST 2021
Run timerTask 2:Wed Mar 17 10:51:03 CST 2021
Run timerTask 1:Wed Mar 17 10:51:06 CST 2021
Run timerTask 1:Wed Mar 17 10:51:09 CST 2021
Run timerTask 2:Wed Mar 17 10:51:09 CST 2021
Run timerTask 1:Wed Mar 17 10:51:12 CST 2021
Run timerTask 1:Wed Mar 17 10:51:15 CST 2021
Run timerTask 2:Wed Mar 17 10:51:15 CST 2021
Run timerTask 1:Wed Mar 17 10:51:18 CST 2021
Run timerTask 1:Wed Mar 17 10:51:21 CST 2021
Run timerTask 2:Wed Mar 17 10:51:21 CST 2021
Run timerTask 1:Wed Mar 17 10:51:24 CST 2021
Run timerTask 1:Wed Mar 17 10:51:27 CST 2021
Run timerTask 2:Wed Mar 17 10:51:27 CST 2021
Run timerTask 1:Wed Mar 17 10:51:30 CST 2021
看到执行结果,走着走着时间就不准了,如果开启更多会更乱。Timer
是单线程的,假如有任务 A、B、C,任务 A 如果执行时间比较长,那么就会影响任务 B、C 的启动和执行时间,如果 B、C 执行时间也比较长,那就会相互影响。
问题 2:任务异常影响其他任务
public static void main(String[] args) {
// 定义任务 1
TimerTask timerTask1 = new TimerTask() {
@Override
public void run() {
/*try {
TimeUnit.SECONDS.sleep(3);// 休眠 3 秒
} catch (InterruptedException e) {
e.printStackTrace();
}*/
int num = 119 / 0;// 没困难创造一个困难
System.out.println("Run timerTask 1:" + new Date());
}
};
// 定义任务 2
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
System.out.println("Run timerTask 2:" + new Date());
}
};
// 计时器
Timer timer = new Timer();
// 添加执行任务(延迟 1s 执行,每 3s 执行一次)
timer.schedule(timerTask1, 1000, 3000);
timer.schedule(timerTask2, 1000, 3000);
}
执行结果:
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
at com.stock.messenger.ui.fragment.Test$1.run(Test.java:24)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Process finished with exit code 0
一个任务出现异常,直接全部挂掉了,其他任务也无法继续了,说明Timer
不会捕获异常,如果 A、B、C 任何一个任务在执行过程中发生异常,就会导致 Timer
整个定时任务停止工作。
小结:
Timer
类实现定时任务的优点是使用简单方便,因为它是 JDK 自定的定时任务,但缺点是任务如果执行时间太长或者是任务执行异常,会影响其他任务调度,在正式的生产环境下建议谨慎使用。所以,如果在使用 Timer
的过程中要注意这些缺陷,虽然可以用,但不推荐。
2. ScheduledExecutorService
因 Timer
有一些缺陷,所以不太建议使用 Timer
,推荐使用 ScheduledExecutorService
:ScheduledExecutorService
也是 JDK 1.5 自带的 API,我们可以使用它来实现定时任务的功能,也就是说 ScheduledExecutorService
可以实现 Timer
类具备的所有功能,并且它可以解决了 Timer
类存在的所有问题。ScheduledExecutorService
即是 Timer
的替代者,JDK 1.5 并发包引入,是基于线程池设计的定时任务类:
可以查看我的另一篇文章:Java/Android中ScheduledExecutorService定时器任务
java.util.concurrent.Executors.newScheduledThreadPool
上了线程池,每个调度任务都会分配到线程池中的某一个线程去执行,任务就是并发调度执行的,任务之间互不影响。
几个重要的调度方法:
schedule()
:只执行一次调度;scheduleAtFixedRate()
:按固定频率调度,如果执行时间过长,下一次调度会延迟,不会同时执行;scheduleWithFixedDelay()
:延迟调度,上一次执行完再加上延迟时间后执行。
任务是支持 Runnable
和 Callable
调度的。
使用:
public static void main(String[] args) {
// 创建任务队列
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(4); // 4 为线程数量
// 执行任务,可以以固定的频率一直执行任务
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("Run Schedule:" + new Date());
}, 0, 2, TimeUnit.SECONDS); // 0s 后开始执行,每 2s 执行一次
}
执行结果:
Run Schedule:Wed Mar 17 11:03:27 CST 2021
Run Schedule:Wed Mar 17 11:03:29 CST 2021
Run Schedule:Wed Mar 17 11:03:31 CST 2021
Run Schedule:Wed Mar 17 11:03:33 CST 2021
Run Schedule:Wed Mar 17 11:03:35 CST 2021
......
2.1 任务超时执行测试:
ScheduledExecutorService
可以解决 Timer
任务之间相应影响的缺点,首先来测试一个任务执行时间过长,会不会对其他任务造成影响,测试代码如下:
public static void main(String[] args) {
// 创建任务队列
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(4);
// 执行任务 1
scheduledExecutorService.scheduleAtFixedRate(() -> {
try {
// 休眠 5 秒
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Run Schedule1:" + new Date());
}, 0, 3, TimeUnit.SECONDS); // 0s 后开始执行,每 3s 执行一次
// 执行任务 2
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("Run Schedule2:" + new Date());
}, 0, 3, TimeUnit.SECONDS); // 0s 后开始执行,每 3s 执行一次
}
执行结果正常:
Run Schedule2:Wed Mar 17 11:05:24 CST 2021
Run Schedule2:Wed Mar 17 11:05:27 CST 2021
Run Schedule1:Wed Mar 17 11:05:29 CST 2021
Run Schedule2:Wed Mar 17 11:05:30 CST 2021
Run Schedule2:Wed Mar 17 11:05:33 CST 2021
Run Schedule1:Wed Mar 17 11:05:34 CST 2021
Run Schedule2:Wed Mar 17 11:05:36 CST 2021
Run Schedule2:Wed Mar 17 11:05:39 CST 2021
Run Schedule1:Wed Mar 17 11:05:39 CST 2021
......
从上述结果可以看出,当任务 1 执行时间 5s 超过了执行频率 3s 时,并没有影响任务 2 的正常执行,因此使用 ScheduledExecutorService
可以避免任务执行时间过长对其他任务造成的影响。
2.2 任务异常测试
接下来我们来测试一下 ScheduledExecutorService
在一个任务异常时,是否会对其他任务造成影响,测试代码如下:
public static void main(String[] args) {
// 创建任务队列
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(4);
// 执行任务 1
scheduledExecutorService.scheduleAtFixedRate(() -> {
int num = 119 / 0; // 没困难创造一个困难
System.out.println("Run Schedule1:" + new Date());
}, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
// 执行任务 2
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("Run Schedule2:" + new Date());
}, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
}
结果如下:
Run Schedule2:Wed Mar 17 11:08:35 CST 2021
Run Schedule2:Wed Mar 17 11:08:38 CST 2021
Run Schedule2:Wed Mar 17 11:08:41 CST 2021
Run Schedule2:Wed Mar 17 11:08:44 CST 2021
Run Schedule2:Wed Mar 17 11:08:47 CST 2021
Run Schedule2:Wed Mar 17 11:08:50 CST 2021
......
从上述结果可以看出,当任务 1 死掉后,并不会影响任务 2 的执行。
小结
在单机生产环境下建议使用 ScheduledExecutorService
来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且不会造成任务间的相互影响。
3. DelayQueue 实现延迟任务
DelayQueue
是一个支持延时获取元素的无界阻塞队列,队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit)
和 compareTo(Delayed)
方法,DelayQueue
实现延迟队列的完整代码如下:
// 需要抛出异常
public static void main(String[] args) throws InterruptedException {
DelayQueue delayQueue = new DelayQueue();
// 添加延迟任务
delayQueue.put(new DelayElement(1000));
delayQueue.put(new DelayElement(3000));
delayQueue.put(new DelayElement(5000));
System.out.println("开始时间:" + DateFormat.getDateTimeInstance().format(new Date()));
while (!delayQueue.isEmpty()){
// 执行延迟任务
System.out.println(delayQueue.take());
}
System.out.println("结束时间:" + DateFormat.getDateTimeInstance().format(new Date()));
}
static class DelayElement implements Delayed {
// 延迟截止时间(单面:毫秒)
long delayTime = System.currentTimeMillis();
public DelayElement(long delayTime) {
this.delayTime = (this.delayTime + delayTime);
}
@Override
// 获取剩余时间
public long getDelay(TimeUnit unit) {
return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
// 队列里元素的排序依据
public int compareTo(Delayed o) {
if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
return 1;
} else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
return -1;
} else {
return 0;
}
}
@Override
public String toString() {
return DateFormat.getDateTimeInstance().format(new Date(delayTime));
}
}
以上程序执行的结果为:
开始时间:2021-3-17 11:14:22
2021-3-17 11:14:23
2021-3-17 11:14:25
2021-3-17 11:14:27
结束时间:2021-3-17 11:14:27
4. Sleep休眠大法
作为补充,最常用的 sleep
休眠大法,不只是当作休眠用,我们还可以利用它很轻松的能实现一个简单的定时任务。
实现逻辑:
这种方式比较傻瓜化了,新开线程添加一个 for / while
死循环,在里面添加sleep
休眠逻辑,让程序每隔 n 秒休眠再执行一次,这样就达到了一个简单定时任务的效果。只能按固定频率运行,不能指定具体运行的时间。
实现代码如下:
public static void main(String[] args) {
// 休眠实现定时任务
new Thread(() -> {
while (true) {
System.out.println("hi, I'm sleep.");
try {
// 每隔2秒执行一次
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
最后
以上就是直率过客为你收集整理的Android定时任务1. Timer2. ScheduledExecutorService3. DelayQueue 实现延迟任务4. Sleep休眠大法的全部内容,希望文章能够帮你解决Android定时任务1. Timer2. ScheduledExecutorService3. DelayQueue 实现延迟任务4. Sleep休眠大法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复