12.9 定时任务
java.util.Timer
和 Executors.newScheduledThreadPool()
都能用来安排任务执行,不过它们的适用场景存在差异。
1. java.util.Timer
- 特性
- 采用单线程执行定时任务,要是有任务执行时间过长,就会影响其他任务的调度。
- 对异常的处理不够健壮,一旦某个任务抛出未检查异常,整个 Timer 都会终止运行。
- 基于绝对时间来调度任务,要是系统时间发生改变,任务调度也会受到影响。
- 适用场景
- 适用于简单的定时任务,并且这些任务不会抛出异常,同时也不存在任务间的依赖关系。
- 对资源使用有严格限制的小型应用程序。
- 案例
import java.util.Timer; import java.util.TimerTask;public class TimerExample {public static void main(String[] args) {Timer timer = new Timer();// 延迟1秒后执行,之后每隔2秒执行一次timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("Timer task executed at: " + System.currentTimeMillis());// 任务执行后可以显式终止Timer(可选)// timer.cancel(); }}, 1000, 2000);} }
2. Executors.newScheduledThreadPool()
- 特性
- 属于线程池,能够配置多个线程来执行定时任务,防止任务之间出现阻塞的情况。
- 具备完善的异常处理机制,单个任务抛出异常不会对其他任务造成影响。
- 基于相对时间进行任务调度,不受系统时间变化的干扰。
- 适用场景
- 适合执行复杂的定时任务,例如需要多线程并行执行、任务之间有依赖关系或者可能会抛出异常的任务。
- 企业级应用中对可靠性和稳定性要求较高的场景。
- 案例
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolExample {public static void main(String[] args) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);// 延迟1秒后执行,之后每隔2秒执行一次 固定频率执行(无论任务耗时)executor.scheduleAtFixedRate(() -> {System.out.println("Scheduled task executed at: " + System.currentTimeMillis());}, 1, 2, TimeUnit.SECONDS);// 延迟1秒后执行,执行完之后隔2秒再执行 固定延迟执行(上一次结束后延迟2秒执行下一次)executor.scheduleWithFixedDelay(() -> {System.out.println("Scheduled task executed at: " + System.currentTimeMillis());// 可以手动关闭线程池,停止任务执行// executor.shutdown(); }, 1, 2, TimeUnit.SECONDS);} }
3. 对比总结
对比项 | java.util.Timer | ScheduledThreadPool |
---|---|---|
线程模型 | 单线程 | 多线程 |
异常处理 | 任务异常会终止整个 Timer | 单个任务异常不会影响其他任务 |
时间依赖性 | 依赖系统时间 | 基于相对时间 |
适用复杂度 | 简单任务 | 复杂任务 |
资源消耗 | 低 | 高(需管理线程池) |
自动停止 | 任务执行后需调用 timer.cancel() 手动停止(可选) | 任务执行后线程池不会自动关闭,需调用 executor.shutdown() |
异常处理 | 若任务抛出异常,Timer 会终止,后续任务无法执行 | 单个任务异常不影响线程池,其他任务可继续执行 |
资源释放 | 若不调用 cancel() ,Timer 持有的线程不会释放 | 若不调用 shutdown() ,线程池会一直运行(浪费资源) |
4. 建议使用场景
- 优先考虑 ScheduledThreadPool:在企业级应用或者需要高可靠性的场景中,应优先选择
Executors.newScheduledThreadPool()
。 - 考虑 Timer:在资源受限的环境下,并且任务逻辑十分简单时,可以考虑使用
Timer
。