Java 9并发编程指南 目录
定制在调度线程池中运行的任务
- 准备工作
- 实现过程
- 工作原理
- 扩展学习
- 更多关注
调度线程池是Executor框架基本线程池的扩展调度在一段时间后执行任务。ScheduledThreadPoolExecutor类实现此线程池并且允许如下两种任务的执行
- **延迟任务**在一段时间后只执行一次的任务
- **周期任务**在延迟之后且持续周期性执行的任务
延迟任务能够执行Callable和Runnable对象但周期任务只能执行Runnable对象。所有通过调度线程池执行的任务都是RunnableScheduledFuture接口实现。本节将学习如何实现自定义RunnableScheduledFuture接口来执行延迟和周期任务。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例
创建名为MyScheduledTask的类此类由名为V的泛型类型参数化继承FutureTask类且实现RunnableScheduledFuture接口
public class MyScheduledTask extends FutureTask implements RunnableScheduledFuture {
声明名为task的私有RunnableScheduledFuture属性
private RunnableScheduledFuture task;
声明名为executor的私有ScheduledThreadPoolExecutor类
private ScheduledThreadPoolExecutor executor;
声明名为period的私有long属性
private long period;
声明名为startDate的私有long属性
private long startDate;
实现类构造函数接收任务将要执行的Runnable对象任务返回的结果创建MyScheduledTask对象的RunnableScheduledFuture任务和将要执行任务的ScheduledThreadPoolExecutor对象。调用其父类构造函数并存储task和executor属性
public MyScheduledTask(Runnable runnable, V result, RunnableScheduledFuture task, ScheduledThreadPoolExecutor executor) {super(runnable, result);this.tasktask;this.executorexecutor;}
实现getDelay()方法如果任务是周期的且startDate属性值大于零计算实际时间和startDate的差值作为返回值。否则返回task属性中存储的初始任务延迟时间。切记必须在作为参数传递的时间单元中返回结果
Overridepublic long getDelay(TimeUnit unit) {if (!isPeriodic()) {return task.getDelay(unit);} else {if (startDate0){return task.getDelay(unit);} else {Date nownew Date();long delaystartDate-now.getTime();return unit.convert(delay, TimeUnit.MILLISECONDS);}}}
实现compareTo()方法调用初始任务的compareTo()方法
Overridepublic int compareTo(Delayed o) {return task.compareTo(o);}
实现isPerodic()方法调用初始任务的isPerodic()方法
Overridepublic boolean isPeriodic() {return task.isPeriodic();}
实现run()方法如果是周期任务则必须使用任务下次执行的开始时间更新startDate属性属性值为实际时间和周期的总和。然后再次把任务添加到ScheduledThreadPoolExecutor对象队列中
Overridepublic void run() {if (isPeriodic() new Date();startDatenow.getTime()period;executor.getQueue().add(this);}
输出实际时间到控制台调用runAndReset()方法执行此任务然后输出再次执行的实际时间到控制台
System.out.printf("Pre-MyScheduledTask: %s\n",new Date());System.out.printf("MyScheduledTask: Is Periodic: %s\n",isPeriodic());super.runAndReset();System.out.printf("Post-MyScheduledTask: %s\n",new Date());}
实现setPeriod()方法设置任务周期
public void setPeriod(long period) {this.periodperiod;}}
创建名为MyScheduledThreadPoolExecutor的类实现执行MyScheduledTask任务的ScheduledThreadPoolExecutor对象。指定此类继承ScheduledThreadPoolExecutor类
public class MyScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor{
实现只调用父类构造函数的类构造函数
public MyScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize);}
实现decorateTask()方法将待执行的Runnable对象和执行此对象的RunnableScheduledFuture任务作为参数接收创建并返回MyScheduledTask任务使用这些对象来构造它们
Overrideprotected RunnableScheduledFuture decorateTask(Runnable runnable, RunnableScheduledFuture task) {MyScheduledTask myTasknew MyScheduledTask(runnable, null, task,this);return myTask;}
重写scheduledAtFixedRate()方法调用其父类方法将返回对象转换成MyScheduledTask对象使用setPeriod()方法设置任务的周期
Overridepublic ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {ScheduledFuture task super.scheduleAtFixedRate(command, initialDelay, period, unit);MyScheduledTask myTask(MyScheduledTask)task;myTask.setPeriod(TimeUnit.MILLISECONDS.convert(period,unit));return task;}}
创建名为Task的类实现Runnable接口
public class Task implements Runnable {
实现run()方法输出任务启动信息设置当前线程休眠2秒钟然后输出任务结束信息
Overridepublic void run() {System.out.printf("Task: Begin.\n");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.printf("Task: End.\n");}}
通过创建名为Main的类添加main()方法实现本范例主类
public class Main {public static void main(String[] args) throws Exception{
创建名为executor的MyScheduledThreadPoolExecutor对象使用4作为参数池中包含两个线程
MyScheduledThreadPoolExecutor executornew MyScheduledThreadPoolExecutor(4);
创建名为task的Task对象输出实际时间到控制台
Task tasknew Task();System.out.printf("Main: %s\n",new Date());
使用schedule()方法发送延迟任务到执行器此任务将延迟1秒后执行
executor.schedule(task, 1, TimeUnit.SECONDS);
设置主线程休眠3秒钟
TimeUnit.SECONDS.sleep(3);
创建另一个Task对象再次输出实际时间到控制台
tasknew Task();System.out.printf("Main: %s\n",new Date());
使用scheduleAtFixedRate()方法发送周期任务到执行器此任务将延迟1秒后执行且每3秒钟执行一次
executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
设置主线程休眠10秒钟
TimeUnit.SECONDS.sleep(10);
使用shutdown()方法关闭执行器使用awaitTermination()方法等待执行器结束
executor.shutdown();executor.awaitTermination(1, TimeUnit.DAYS);
输出指明程序结束的信息到控制台
System.out.printf("Main: End of the program.\n");}}
工作原理
在本节中实现了MyScheduledTask类用来实现在ScheduledThreadPoolExecutor执行器上执行的定制化任务。此类继承FutureTask类和实现RunnableScheduledFuture接口。它实现RunnableScheduledFuture接口是因为所有在调度执行器中运行的任务必须实现此接口且继承FutureTask类。这是因为此类正确地实现了RunnableScheduledFuture接口中声明的方法。之前提到的所有接口和类都是参数化类它们具有通过任务返回的数据类型。
为了在调度执行器中使用MyScheduledTask任务重写了MyScheduledThreadPoolExecutor类的decorateTask()方法。此类继承ScheduledThreadPoolExecutor执行器并且此方法将ScheduledThreadPoolExecutor执行器实现的默认调度任务转换成MyScheduledTask任务。所以当实现自定义的调度任务时还需要实现自定义调度执行器。
decorateTask()方法创建新的MyScheduledTask对象包含四个参数。第一个参数是任务中待执行的Runnable对象第二个参数是通过任务待返回的对象第三个参数是线程池中待替换的新对象最后一个对象是将要执行任务的执行器。本范例中使用this关键字来引用正在创建任务的执行器。
MyScheduledTask类可以执行延迟和周期任务通过实现getDelay()和run()方法包含必要的逻辑操作来执行这两种任务。
调度执行器通过调用getDelay()方法知道是否需要执行任务此方法特性在延迟和周期任务中改变。如前所述MyScheduledClass类构造函数接收准备执行 Runnable对象的初始ScheduledRunnableFuture对象且存储此对象为类属性用来访问类方法和数据。当执行延迟任务时getDelay()方法返回初始任务的延迟时间。但处理周期任务时getDelay()方法返回任务执行的实际时间和startDate的差值。
run()方法是执行任务的方法周期任务特点是如果希望再次执行任务则必须将下一个执行任务作为新任务放在执行器的队列中。所以如果执行周期任务则设置startDate属性值为任务执行的实际时间和区间并将任务再次存储在执行器的队列中。startDate属性存储下一个任务开始执行时间然后使用FutureTask类提供的runAndReset()方法执行任务。处理延迟任务不需要将任务放置到执行器队列中因为延迟任务只执行一次。
还需要注意执行器是否已经关闭。如果是则不需要再次将周期任务存储到执行器队列中。
最后重写MyScheduledThreadPoolExecutor类的scheduleAtFixedRate()方法。我们之前提到对于周期任务使用任务周期设置startDate属性值但是我们还没有初始化周期。所以需要重写此方法将周期作为参数接收将周期传给MyScheduledTask类才能使用。
本范例完成了实现Runnable接口的任务类它是在调度执行器中执行的任务。范例主类创建MyScheduledThreadPoolExecutor执行器且返回如下两个任务
- 延迟任务在实际时间1秒后执行
- 周期认为在实际时间1秒后执行然后每个3秒执行一次
下图显示本范例在控制台输出的执行信息可以检查是否正确执行这两种任务
扩展学习
ScheduledThreadPoolExecutor类提供decorateTask()方法的另一个版本此方法将Callable对象代替Runnable对象作为参数接收。
更多关注
- 第四章“线程执行器”中的“执行器中延迟运行任务”和“执行器中周期运行任务”小节