执行器框架提供ThreadPoolExecutor类使用线程池来执行并发任务,以避免所有线程创建操作。当发送任务到执行器时,根据执行器的配置迅速执行。当结束时,任务从执行器中删除,如果想再次运行,就需要再次发送任务到执行器中。
然而,执行器框架通过ScheduledThreadPoolExecutor类提供周期运行任务的可行性。本节中,讲学习如何使用此类的功能计划周期任务。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
创建名为Task的类,实现Runnable接口:
public class Task implements Runnable {
-
定义名为name的私有String属性,存储任务名称:
private final String name;
-
实现类构造函数,初始化属性:
public Task(String name){ this.name = name; }
-
实现run()方法,输出当前时间的信息到控制台,指明任务将在特定周期内执行:
@Override public void run() { System.out.printf("%s : Executed at : %s\n", name, new Date()); }
-
实现范例的主方法,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
使用Executors类的newScheduledThreadPool()方法创建ScheduledExecutorService,传参值1:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
-
输出当前时间到控制台:
System.out.printf("Main : Starting at : %s\n", new Date());
-
创建新的Task对象:
Task task = new Task("Task");
-
使用scheduledAtFixRate()方法传递对象到执行器。使用上一步创建的任务作为参数:数字1,数字2,以及TimeUnit.SECONDS常量。此方法返回ScheduledFuture对象用来控制任务状态:
ScheduledFuture<?> result =executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
-
创建循环重复10次输出任务下一次执行持续的时间。在循环中,使用ScheduledFuture对象的getDelay()方法在任务下一次执行之前,获得毫秒数:
for (int i = 0 ; i < 10 ; i ++){ System.out.printf("Main : Delay : %d\n", result.getDelay(TimeUnit.MILLISECONDS));
-
休眠线程500毫秒:
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e1) { e1.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());
工作原理
在使用执行器框架运行周期任务时,需要使用ScheduledExecutorService类。为了创建此对象(对每个执行器来说),Java要求使用Executors类,相当于executor对象的工厂类。这种情况下,使用newScheduledThreadPool()方法创建ScheduledExecutorService对象。此对象将线程池中线程数量作为参数接收,因为本范例中只有一个任务,所以传参值为1。
一旦执行器需要运行周期任务,使用scheduleAtFixedRate()方法发送任务到执行器。此方法接受四个参数:周期运行的任务,任务第一次执行的延迟时间,两次执行间的周期,第二和第三个参数的时间单位,是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。
需要注意的重点是两次执行之间的时间是启动这两个执行任务的时间间隔。如果一个周期任务需要5秒后开始执行,而任务的周期间隔是3秒,则需要同时定义两个任务执行实例。
scheduledAtFixedRate()方法返回继承Future接口的ScheduledFuture对象,包含处理计划任务的方法。ScheduledFuture是参数化接口,本范例中,因为任务是未参数化的Runnable对象,需要使用?标识作为参数来进行参数化。
范例中用到ScheduledFuture接口的getDelay()方法,返回直到下一个任务执行的时间。此方法接收TimuUnit常量,包含想要接收结果的时间单位。
下图显示本范例在控制台输出的执行信息:
任务每个两秒执行一次(通过Task前缀定义)且延迟500毫秒输出到控制台,即设置主线程休眠的时长。当关闭执行器时,计划任务终止运行,控制台上将不显示任何信息。
扩展学习
ScheduledThreadPoolExecutor提供scheduleWithFixedRate()方法来安排周期任务,此方法与scheduledAtFixedRate()方法参数相同,然而值得注意的不同点是,scheduledAtFixedRate()方法的第三个参数确定两个任务开始执行的周期时间。而scheduleWithFixedRate()方法的第三个参数确定是任务结束与下一个任务开始的周期时间。
通过shutdown()方法配置ScheduledThreadPoolExecutor类实例的特性,实例默认特性是计划任务在调用此方法时结束,通过使用ScheduledThreadPoolExecutor类的setContinueExistingPeriodicTasksAfterShutdownPolicy()方法传递true值来配置实例特性,在调用shutdown()方法之前,周期任务不会结束。
更多关注
- 本章“创建线程执行器并控制其被拒任务”和”执行器中延迟运行任务“小节。