在执行者中周期性地运行任务

Executor 框架提供了 ThreadPoolExecutor 类用一个线程池来执行 Callable 和 Runnable 任务,避免了所有这些线程创建的操作。 当您将一个任务发送到执行者,它将根据执行者的配置尽快执行。 当它结束,任务从执行者处被删除。如果您想再次执行,您必须将其再次发给执行者。

Executor 框架通过 ScheduledThreadPoolExecutor 类提供了执行周期性的任务的可能性。

任务

周期执行一个任务。

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt4.sect08 package中

  • Runnable 实现类 : Task

    • 有 String 类型属性 name 表示 task 名称
    • run() 方法只是简单地输出 task 名称和当前时间
    public class Task implements Runnable {
        private String name;
    
        public Task(String name) {
            this.name = name;
        }
    
        @Override
        public void run() {
            System.out.printf("%s: Starting at : %s\n", name, new Date());
        }
    }
    
  • 控制类 : Main

    • 使用 Executors 类的 newScheduledThreadPool() 方法创建一个 ScheduledThreadPoolExecutor。参数值传 1。
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
    
    • 创建新的 Task 对象,并用 scheduleAtFixedRate() 方法发送给执行者。 此方法返回一个 ScheduledFuture 对象可用以控制任务的状态
        System.out.printf("Main: Starting at: %s\n", new Date());
        Task task = new Task("Task");
        ScheduledFuture<?> result = executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
    
    • 每隔 0.5 秒使用 ScheduledFuture 对象的 getDelay() 方法检查离任务的下一次执行还有多长时间
        for (int i = 0; i < 10; i++) {
            System.out.printf("Main: Delay: %d\n", result.getDelay(TimeUnit.MILLISECONDS));
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    • 使用 shutdown() 方法结束执行者
        executor.shutdown();
    
    • 再等待 5 秒核实周期性任务已结束
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        System.out.printf("Main: Finished at: %s\n", new Date());
    

工作原理

当要用 Executor 框架执行一个周期任务,需要一个 ScheduledExecutorService 对象。 Java 推荐使用 Executors 类来创建它。Executors 类作为一个执行者对象的工厂类。 在本例中,应使用 newScheduledThreadPool() 方法来创建一个 ScheduledExecutorService 对象。 此方法接受线程池中线程数目作为一个参数。因为本例中只有一个任务,所以参数值为 1。

有了执行一个周期任务所需的执行者,就可以发送任务到此执行者。本例中使用了 scheduledAtFixedRate() 方法。 此方法接受 4 个参数:

  1. 要周期执行的任务
  2. 到任务第一次执行时的延迟时间
  3. 2次执行的间隔时间
  4. 第2和第3个参数的时间单位(TimeUnit类的常量)

两次执行之间的时长是这两次执行 开始时间 的间隔时长。如果您有一个周期任务持续 5 秒而间隔时间是 3 秒,每次将有2个任务的实例运行。

scheduleAtFixedRate() 方法返回一个 ScheduledFuture 对象,ScheduledFuture 扩展 Future 接口,有一些方法干计划任务的活。 ScheduledFuture 是一个参数化的接口。本例中,因为任务是一个没有参数化的 Runnable 对象,所以必须用 ? 符号作为参数来参数化它。

您已使用了 ScheduledFuture 接口的一个方法。getDelay() 方法返回离任务的下一次执行的时间。 此方法接受一个 TimeUnit 常量,返回结果将以此常量作为单位。

了解更多

ScheduledThreadPoolExecutor 提供了其他的方法来计划周期性任务。就是 scheduleWithFixedDelay() 方法。 它的参数和 scheduledAtFixedRate() 方法一致,但是其区别值得一提。

  • scheduledAtFixedRate() 方法: 第3个参数决定2次执行的开始时间的间隔时间
  • scheduleWithFixedDelay() 方法: 第3个参数决定这次执行结束到下一次执行开始的间隔时间.

您也可配置 ScheduledThreadPoolExecutor 类的实例的 shutdown() 方法的行为。 默认行为是计划任务在调用 shutdown() 方法时结束。 您能使用 ScheduledThreadPoolExecutor 类的 setContinueExistingPeriodicTasksAfterShutdownPolicy() 方法改变此行为。 参数为 true 时,周期任务在调用 shutdown() 方法时不会结束。