定制在调度线程池中运行的任务

Java 9并发编程指南 目录

定制在调度线程池中运行的任务

调度线程池是Executor框架基本线程池的扩展,调度在一段时间后执行任务。ScheduledThreadPoolExecutor类实现此线程池,并且允许如下两种任务的执行:

  • **延迟任务:**在一段时间后只执行一次的任务
  • **周期任务:**在延迟之后且持续周期性执行的任务

延迟任务能够执行Callable和Runnable对象,但周期任务只能执行Runnable对象。所有通过调度线程池执行的任务都是RunnableScheduledFuture接口实现。本节将学习如何实现自定义RunnableScheduledFuture接口,来执行延迟和周期任务。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为MyScheduledTask的类,此类由名为V的泛型类型参数化,继承FutureTask类且实现RunnableScheduledFuture接口:

    public class MyScheduledTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
    
  2. 声明名为task的私有RunnableScheduledFuture属性:

    	private RunnableScheduledFuture<V> task;
    
  3. 声明名为executor的私有ScheduledThreadPoolExecutor类:

    	private ScheduledThreadPoolExecutor executor;
    
  4. 声明名为period的私有long属性:

    	private long period;
    
  5. 声明名为startDate的私有long属性:

    	private long startDate;
    
  6. 实现类构造函数,接收任务将要执行的Runnable对象,任务返回的结果,创建MyScheduledTask对象的RunnableScheduledFuture任务,和将要执行任务的ScheduledThreadPoolExecutor对象。调用其父类构造函数,并存储task和executor属性:

    	public MyScheduledTask(Runnable runnable, V result, RunnableScheduledFuture<V> task, ScheduledThreadPoolExecutor executor) {
    		super(runnable, result);
    		this.task=task;
    		this.executor=executor;
    	}
    
  7. 实现getDelay()方法,如果任务是周期的且startDate属性值大于零,计算实际时间和startDate的差值作为返回值。否则返回task属性中存储的初始任务延迟时间。切记必须在作为参数传递的时间单元中返回结果:

    	@Override
    	public long getDelay(TimeUnit unit) {
    		if (!isPeriodic()) {
    			return task.getDelay(unit);
    		} else {
    			if (startDate==0){
    				return task.getDelay(unit);
    			} else {
    				Date now=new Date();
    				long delay=startDate-now.getTime();
    				return unit.convert(delay, TimeUnit.MILLISECONDS);
    			}
    		}
    	}
    
  8. 实现compareTo()方法,调用初始任务的compareTo()方法:

    	@Override
    	public int compareTo(Delayed o) {
    		return task.compareTo(o);
    	}
    
  9. 实现isPerodic()方法,调用初始任务的isPerodic()方法:

    	@Override
    	public boolean isPeriodic() {
    		return task.isPeriodic();
    	}
    
  10. 实现run()方法,如果是周期任务,则必须使用任务下次执行的开始时间更新startDate属性,属性值为实际时间和周期的总和。然后再次把任务添加到ScheduledThreadPoolExecutor对象队列中:

    	@Override
    	public void run() {
    		if (isPeriodic() && (!executor.isShutdown())) {
    			Date now=new Date();
    			startDate=now.getTime()+period;
    			executor.getQueue().add(this);
    		}
    
  11. 输出实际时间到控制台,调用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());
    	}
    
  12. 实现setPeriod()方法设置任务周期:

    	public void setPeriod(long period) {
    		this.period=period;
    	}
    }
    
  13. 创建名为MyScheduledThreadPoolExecutor的类,实现执行MyScheduledTask任务的ScheduledThreadPoolExecutor对象。指定此类继承ScheduledThreadPoolExecutor类:

    public class MyScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor{
    
  14. 实现只调用父类构造函数的类构造函数:

    	public MyScheduledThreadPoolExecutor(int corePoolSize) {
    		super(corePoolSize);
    	}
    
  15. 实现decorateTask()方法,将待执行的Runnable对象和执行此对象的RunnableScheduledFuture任务作为参数接收,创建并返回MyScheduledTask任务,使用这些对象来构造它们:

    	@Override
    	protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
    		MyScheduledTask<V> myTask=new MyScheduledTask<V>(runnable, null, task,this);
    		return myTask;
    	}
    
  16. 重写scheduledAtFixedRate()方法,调用其父类方法,将返回对象转换成MyScheduledTask对象,使用setPeriod()方法设置任务的周期:

    	@Override
    	public 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;
    	}
    }
    
  17. 创建名为Task的类,实现Runnable接口:

    public class Task implements Runnable {
    
  18. 实现run()方法,输出任务启动信息,设置当前线程休眠2秒钟,然后输出任务结束信息:

    	@Override
    	public void run() {
    	System.out.printf("Task: Begin.\n");
    		try {
    			TimeUnit.SECONDS.sleep(2);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.printf("Task: End.\n");
    	}
    }
    
  19. 通过创建名为Main的类,添加main()方法,实现本范例主类:

    public class Main {
    	public static void main(String[] args)  throws Exception{
    
  20. 创建名为executor的MyScheduledThreadPoolExecutor对象,使用4作为参数,池中包含两个线程:

    		MyScheduledThreadPoolExecutor executor=new MyScheduledThreadPoolExecutor(4);
    
  21. 创建名为task的Task对象,输出实际时间到控制台:

    		Task task=new Task();
    		System.out.printf("Main: %s\n",new Date());
    
  22. 使用schedule()方法发送延迟任务到执行器,此任务将延迟1秒后执行:

    		executor.schedule(task, 1, TimeUnit.SECONDS);
    
  23. 设置主线程休眠3秒钟:

    		TimeUnit.SECONDS.sleep(3);
    
  24. 创建另一个Task对象,再次输出实际时间到控制台:

    		task=new Task();
    		System.out.printf("Main: %s\n",new Date());
    
  25. 使用scheduleAtFixedRate()方法发送周期任务到执行器,此任务将延迟1秒后执行,且每3秒钟执行一次:

    		executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
    
  26. 设置主线程休眠10秒钟:

    		TimeUnit.SECONDS.sleep(10);
    
  27. 使用shutdown()方法关闭执行器,使用awaitTermination()方法等待执行器结束:

    		executor.shutdown();
    		executor.awaitTermination(1, TimeUnit.DAYS);
    
  28. 输出指明程序结束的信息到控制台:

    		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秒执行一次

下图显示本范例在控制台输出的执行信息,可以检查是否正确执行这两种任务:

pics/08_04.jpg

扩展学习

ScheduledThreadPoolExecutor类提供decorateTask()方法的另一个版本,此方法将Callable对象代替Runnable对象作为参数接收。

更多关注

  • 第四章“线程执行器”中的“执行器中延迟运行任务”和“执行器中周期运行任务”小节

猜你喜欢

转载自blog.csdn.net/nicolastsuei/article/details/84629686