6. 在执行器中延时执行任务
如果不想让任务马上被执行,而想让任务在过一段时间之后才被执行,或者任务能够被周期性地执行。为了达到这个目的,执行器框架提供了ScheduledThreadPoolExecutor类。
下面我们将学习如何创建ScheduledThreadPoolExecutor执行器,以及如何使用它在经过一个给定的时间后开始执行任务。
1. 创建一个名为Task的类,并实现Callable接口,接口的泛型参数为String类型。
import java.util.Date; import java.util.concurrent.Callable; public class Task implements Callable<String> { private String name; public Task(String name){ this.name = name; } @Override public String call() throws Exception { System.out.printf("%s: Starting at : %s\n", name, new Date()); return "Hello, world"; } }
2. 实现范例的主类Main,并实现main()方法。
import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1); System.out.printf("Main: Starting at: %s\n", new Date()); for(int i=0;i<5;i++){ Task task = new Task("Task "+i); executor.schedule(task, i+1, TimeUnit.SECONDS); } //结束执行器 executor.shutdown(); //等待所有的任务执行结束 try { executor.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.printf("Main: Ends at: %s\n", new Date()); } }
3. 程序运行结果如下
Main: Starting at: Sun Oct 25 15:51:23 CST 2015 Task 0: Starting at : Sun Oct 25 15:51:24 CST 2015 Task 1: Starting at : Sun Oct 25 15:51:25 CST 2015 Task 2: Starting at : Sun Oct 25 15:51:26 CST 2015 Task 3: Starting at : Sun Oct 25 15:51:27 CST 2015 Task 4: Starting at : Sun Oct 25 15:51:28 CST 2015 Main: Ends at: Sun Oct 25 15:51:28 CST 2015
也可以使用Runnable接口来实现任务,因为ScheduledThreadPoolExecutor类的schedule()方法可以同时接受这两种类型的任务。
虽然ScheduledThreadPoolExecutor是ThreadPoolExecutor类的子类,因为继承了ThreadPoolExecutor类所有的特性。但是,Java推荐仅在开发定时任务程序时采用ScheduledThreadPoolExecutor类。
在调用shutdown()方法而仍有待处理的任务需要执行时,可以配置ScheduledThreadPoolExecutor的行为。默认的行为是不论执行器是否结束,待处理的任务仍将被执行。但是,通过调用ScheduledThreadPoolExecutor类的setExecuteExistingDelayedTasksAfterShutdownPolicy()方法则可以改变这个行为。传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。
7. 在执行器中周期性执行任务
当发送一个任务给ThreadPoolExecutor类执行器后,根据执行器的配置,它将尽快地执行这个任务。当任务执行结束后,这个任务就会从执行器中删除;如果想再次执行这个任务,则需要再次发送这个任务到执行器。
但是,执行器框架提供了ScheduledThreadPoolExecutor类来执行周期性的任务。
下面我们将学习如何使用这个类的功能来计划执行周期性的任务。
1. 创建一个名为Task的类,并实现Runnable接口。
import java.util.Date; 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()); } }
2. 实现范例的主类Main,并实现main()方法。
import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); System.out.printf("Main: Starting at: %s\n", new Date()); Task task = new Task("Task"); //第一个参数为周期性执行的任务,第二个为第一次执行后延时时间,第三个为两次执行的时间周期,第四个为时间单位 ScheduledFuture<?> result = executor.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS); try { for(int i=0;i<10;i++){ System.out.printf("Main: Delay: %d\n", result.getDelay(TimeUnit.MILLISECONDS)); Thread.sleep(500); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //结束执行器 executor.shutdown(); //将线程休眠5秒,等待周期性的任务全部执行完成 try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.printf("Main: Finished at: %s\n", new Date()); } }
3. 程序运行结果如下
Main: Starting at: Sun Oct 25 16:25:23 CST 2015 Main: Delay: 999 Main: Delay: 499 Main: Delay: 0 Task: Starting at : Sun Oct 25 16:25:24 CST 2015 Main: Delay: 1499 Main: Delay: 999 Main: Delay: 499 Main: Delay: 0 Task: Starting at : Sun Oct 25 16:25:26 CST 2015 Main: Delay: 1499 Main: Delay: 999 Main: Delay: 499 Task: Starting at : Sun Oct 25 16:25:28 CST 2015 Main: Finished at: Sun Oct 25 16:25:33 CST 2015
ScheduledThreadPoolExecutor还提供了其他方法来安排周期性任务的运行,比如scheduleWithFixedRate()方法。这个方法与scheduleAtFixedRate()方法具有相同的参数,但是略有一些不同需要注意。在scheduleAtFixedRate()方法中,第3个参数表示任务两次开始时间的间隔,而在scheduleWithFixedRate()方法中,第3个参数表示任务上一次执行结束的时间与下一次开始执行的时间的间隔。
也可以配置ScheduledThreadPoolExecutor实现shutdown()方法的行为,默认行为是当调用shutdown()方法后,定时任务就结束了。可以通过ScheduledThreadPoolExecutor类的setContinueExistingPeriodicTasksAfterShutdownPolicy()方法来改变这个行为,传递参数为true给这个方法,这样调用shutdown()方法后,周期性任务仍将继续执行。
8. 在执行器中取消任务
有时候,我们可能需要取消已经发送给执行器的任务。在这种情况下,可以使用Future接口的cancel()方法来执行取消操作。
下面我们将学习如何使用这个方法取消已经发送给执行器的任务。
1. 创建一个名为Task的类。
import java.util.concurrent.Callable; public class Task implements Callable<String> { @Override public String call() throws Exception { while(true){ System.out.printf("Task: Test\n"); Thread.sleep(100); } } }
2. 实现范例的主类Mian,并实现main()方法。
import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); Task task = new Task(); System.out.println("Main: Executing the Task"); Future<String> result = executor.submit(task); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Main: Canceling the Task"); //取消任务 result.cancel(true); System.out.println("Main: Cancelled: "+ result.isCancelled()); System.out.println("Main: Done: "+ result.isDone()); executor.shutdown(); System.out.println("Main: The executor has finished"); } }
3. 程序运行结果如下
Main: Executing the Task Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Task: Test Main: Canceling the Task Main: Cancelled: true Main: Done: true Main: The executor has finished
如果想取消一个已经发送给执行器的任务,可以使用Future接口的cancel()方法。根据调用cancel()方法时所传递的参数以及任务的状态,这个方法的行为有些不同。
- 如果任务已经完成,或者之前已经被取消,或者由于某种原因而不能被取消,那么方法将返回false并且任务也不能取消。
- 如果任务在执行器中等待分配Thread对象来执行它,那么任务被取消,并且不会开始执行。如果任务已经在运行,那么它依赖于调用cancel()方法时所传递的参数。如果传递的参数为true并且任务正在运行,那么任务将被取消。如果传递的参数为false并且任务正在运行,那么任务不会被取消。
如果Future对象所控制任务已经被取消,那么使用Future对象的get()方法时将抛出CancellationException异常。
9. 在执行器中控制任务的完成
FutureTask类提供了一个名为done()的方法,允许在执行器任务执行结束之后,还可以执行一些代码。这个方法可以被用来执行一些后期处理操作,比如:产生报表,通过邮件发送结果或者释放一些系统资源。当任务执行完成是受FutureTask类控制时,这个方法在内部被FutureTask类调用。在任务结果设置后以及任务的状态已改变为isDone之后,无论任务是否被取消或者正常结束,done()方法都被调用。
默认情况下,done()方法的实现为空,即没有任何具体的代码实现。我们可以覆盖FutureTask类并实现done()方法来改变这种行为。
下面,我们将学习如何覆盖这些方法,并在任务结束后执行这些代码。
1. 创建名为ExecutableTask的类,并实现Callable接口,接口的泛型为String类型。
import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; public class ExecutableTask implements Callable<String> { private String name; public ExecutableTask(String name){ this.name = name; } @Override public String call() throws Exception { long duration = (long) (Math.random()*10); System.out.printf("%s: Waiting %d seconds for results.\n", name, duration); try { TimeUnit.SECONDS.sleep(duration); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return "Hello, world. I‘m "+name; } public String getName() { return name; } }
2. 实现一个名为ResultTask的类,并继承FutureTask类。