Java 开发笔记 (一百零五) 几种定时器线程池
前面介绍了普通线程池的用法, 就大多数任务而言, 它们对具体的执行时机并无特殊要求, 最多是希望早点跑完早点出结果. 不过对于需要定时执行的任务来说, 它们要求在特定的时间点运行, 并且往往不止运行一次, 还要周期性地反复运行. 由于普通线程池满足不了此类定时运行的需求, 因此 Java 又提供了定时器线程池来实现定时与周期执行任务的功能.
- // 创建一个延迟一次的单线程定时器
- ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();
至于固定数量的定时器线程池则通过 newScheduledThreadPool 方法获得, 它的创建代码示例如下:
- // 创建一个延迟一次的多线程定时器(线程池大小为 3)
- ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
虽然定时器线程池只有两类, 但定时器的调度方式有三种之多, 主要是依据启动次数与周期长度来划分, 详细说明如下:
下面做个实验观察一下两种定时器线程池的运行过程, 实验开始前先定义一个参观任务, 主要用来打印当前的操作日志, 包括操作时间, 操作线程, 操作描述等信息. 参观任务的代码例子如下所示:
- // 定义一个参观任务
- private static class Visit implements Runnable {
- private String name; // 任务名称
- private int index; // 任务序号
- public Visit(String name, int index) {
- this.name = name;
- this.index = index;
- }
- @Override
- public void run() {
- // 以下打印操作日志, 包括操作时间, 操作线程, 操作描述等信息
- String desc = String.format("%s 的第 %d 个任务到此一游", name, index);
- PrintUtils.print(Thread.currentThread().getName(), desc);
- }
- };
然后命令单线程的定时器线程池调用 schedule 方法执行一次的定时任务, 具体的实验代码示例如下:
- // 测试延迟一次的单线程定时器
- private static void testSingleScheduleOnce() {
- // 创建一个延迟一次的单线程定时器
- ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();
- for (int i=0; i<5; i++) { // 循环开展 5 个调度
- // 创建一个参观任务
- Visit visit = new Visit("延迟一次的单线程定时器", i);
- // 命令线程池开展任务调度. 延迟 1 秒后执行参观任务
- pool.schedule(visit, 1, TimeUnit.SECONDS);
- }
- }
运行以上的实验代码, 观察到如下的线程池日志:
15:49:16.122 pool-1-thread-1 延迟一次的单线程定时器的第 0 个任务到此一游
15:49:16.123 pool-1-thread-1 延迟一次的单线程定时器的第 1 个任务到此一游
15:49:16.123 pool-1-thread-1 延迟一次的单线程定时器的第 2 个任务到此一游
15:49:16.124 pool-1-thread-1 延迟一次的单线程定时器的第 3 个任务到此一游
15:49:16.124 pool-1-thread-1 延迟一次的单线程定时器的第 4 个任务到此一游
由日志可见, 该定时器线程池自始至终只有唯一一个的线程在运行.
再来测试固定数量的定时器线程池, 此时换成调用 scheduleAtFixedRate 方法, 准备以固定频率周期性地执行定时任务, 具体的实验代码示例如下:
- // 测试固定速率的多线程定时器
- private static void testMultiScheduleRate() {
- // 创建一个固定速率的多线程定时器(线程池大小为 3)
- ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
- for (int i=0; i<5; i++) { // 循环开展 5 个调度
- // 创建一个参观任务
- Visit visit = new Visit("固定速率的多线程定时器", i);
- // 命令线程池开展任务调度. 第一次延迟 1 秒后执行参观任务, 以后每间隔 3 秒执行下一个参观任务
- pool.scheduleAtFixedRate(visit, 1, 3, TimeUnit.SECONDS);
- }
- }
运行以上的实验代码, 观察到如下的线程池日志:
15:50:21.859 pool-1-thread-1 固定速率的多线程定时器的第 0 个任务到此一游
15:50:21.859 pool-1-thread-2 固定速率的多线程定时器的第 1 个任务到此一游
15:50:21.859 pool-1-thread-3 固定速率的多线程定时器的第 2 个任务到此一游
15:50:21.860 pool-1-thread-3 固定速率的多线程定时器的第 3 个任务到此一游
15:50:21.861 pool-1-thread-3 固定速率的多线程定时器的第 4 个任务到此一游
15:50:24.790 pool-1-thread-3 固定速率的多线程定时器的第 1 个任务到此一游
15:50:24.791 pool-1-thread-3 固定速率的多线程定时器的第 3 个任务到此一游
15:50:24.792 pool-1-thread-3 固定速率的多线程定时器的第 4 个任务到此一游
15:50:24.793 pool-1-thread-2 固定速率的多线程定时器的第 2 个任务到此一游
15:50:24.798 pool-1-thread-1 固定速率的多线程定时器的第 0 个任务到此一游
由日志可见, 该定时器线程池一共开启了三个线程来执行定时任务, 注意到每个任务的前后日志间隔时间不足 3 秒, 正好说明间隔的 3 秒并非前后两次运行的首尾间隔.
那么调用方法改成 scheduleWithFixedDelay, 试试以固定间隔周期性地执行定时任务会是什么样的, 具体的实验代码示例如下:
- // 测试固定延迟的多线程定时器
- private static void testMultiScheduleDelay() {
- // 创建一个固定速率的多线程定时器(线程池大小为 3)
- ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
- for (int i=0; i<5; i++) { // 循环开展 5 个调度
- // 创建一个参观任务
- Visit visit = new Visit("固定延迟的多线程定时器", i);
- // 命令线程池开展任务调度. 第一次延迟 1 秒后执行参观任务, 以后每 3 秒执行下一个参观任务
- pool.scheduleWithFixedDelay(visit, 1, 3, TimeUnit.SECONDS);
- }
- }
运行以上的实验代码, 观察到如下的线程池日志:
16:10:19.281 pool-1-thread-1 固定延迟的多线程定时器的第 0 个任务到此一游
16:10:19.281 pool-1-thread-2 固定延迟的多线程定时器的第 1 个任务到此一游
16:10:19.281 pool-1-thread-3 固定延迟的多线程定时器的第 2 个任务到此一游
16:10:19.283 pool-1-thread-3 固定延迟的多线程定时器的第 3 个任务到此一游
16:10:19.283 pool-1-thread-2 固定延迟的多线程定时器的第 4 个任务到此一游
16:10:22.283 pool-1-thread-1 固定延迟的多线程定时器的第 1 个任务到此一游
16:10:22.284 pool-1-thread-2 固定延迟的多线程定时器的第 3 个任务到此一游
16:10:22.285 pool-1-thread-3 固定延迟的多线程定时器的第 2 个任务到此一游
16:10:22.286 pool-1-thread-3 固定延迟的多线程定时器的第 4 个任务到此一游
16:10:22.287 pool-1-thread-1 固定延迟的多线程定时器的第 0 个任务到此一游
由日志可见, 此时每个任务的前后日志时间均不小于 3 秒, 证明了 scheduleWithFixedDelay 方法的确采取了固定间隔而非固定速率.
来源: https://www.2cto.com/kf/201906/810690.html