当在ForkJoinPool类中执行ForkJoinTask对象时,使用ForkJoinTask类提供的的cancel()方法,在对象开始执行之前取消。当取消任务时,需要关注如下两点:
- ForkJoinPool类不提供任何方法来取消在线程池中运行或等待的所有任务。
- 当取消一个任务时,不能取消这个任务已经执行的任务。
本节中,通过范例实现ForkJoinTask对象的取消。遍历数字数组,找到指定数字的第一个任务将取消剩余任务。由于fork/join框架不提供这种功能,需要实现辅助类来处理取消操作。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为ArrayGenerator的类,用来生成指定长度的随机整数数组。实现名为generateArray() 的方法,生成数字数组,接收参数为数组长度:
public class ArrayGenerator { public int[] generateArray(int size) { int array[] = new int[size]; Random random = new Random(); for(int i = 0 ; i < size ; i ++) { array[i] = random.nextInt(10); } return array; } }
-
创建名为TaskManager的类,用来存储在本范例ForkJoinPool中执行的所有任务。由于ForkJoinPool和ForkJoinTask类的局限性,将要使用此类取消ForkJoinPool类的所有任务:
public class TaskManager {
-
使用参数化的ForkJoinTask类和Integer类定义名为tasks的对象列表:
private final ConcurrentLinkedDeque<SearchNumberTask> tasks;
-
实现类构造函数,初始化任务列表:
public TaskManager(){ tasks=new ConcurrentLinkedDeque<>(); }
-
实现addTask()方法,添加ForkJoinTask对象到任务列表中:
public void addTask(SearchNumberTask task){ tasks.add(task); }
-
实现cancelTasks()方法,使用cancel()方法取消存储在列表中的所有ForkJoinTask对象。此方法接收ForkJoinTask对象作为参数来计划取消其它任务:
public void cancelTasks(SearchNumberTask cancelTask){ for (SearchNumberTask task :tasks) { if (task!=cancelTask) { task.cancel(true); task.logCancelMessage(); } } }
-
实现SearchNumberTask类,继承Integer类参数化的RecursiveTask类。此类在整型数组的一部分元素中检索数字:
public class SearchNumberTask extends RecursiveTask<Integer> {
-
定义名为numbers的私有整型数字数组:
private int numbers[];
-
定义两个名为start和end的私有整型属性,确定任务需要处理的数组元素集:
private int start, end;
-
定义名为number的私有整型属性,存储将要检索的数字:
private int number;
-
定义名为manager的私有TaskManager属性,使用此对象来取消所有任务:
private TaskManager manager;
-
定义初始值为-1的私有整型常量,当任务没有找到检索数字时,返回此常量:
private final static int NOT_FOUND=-1;
-
实现类构造函数,初始化属性:
public SearchNumberTask(int numbers[], int start, int end, int number, TaskManager manager){ this.numbers=numbers; this.start=start; this.end=end; this.number=number; this.manager=manager; }
-
实现compute()方法,首先输出指明start和end属性值的信息到控制台:
@Override protected Integer compute() { System.out.println("Task: "+start+":"+end);
-
如果start和end属性相差大于10(任务需要处理超过10个数组中的元素),调用launchTasks()方法将任务工作拆分成两个子任务:
int ret; if (end-start>10) { ret=launchTasks();
-
否则,在任务需要处理的数组块中检索指定数字,调用lookForNumber()方法:
} else { ret=lookForNumber(); }
-
返回任务结果:
return ret; }
-
实现lookForNumber()方法:
private int lookForNumber() {
-
遍历任务需要处理的所有元素,将元素中存储的数字与检索数字比较,如果相等,输出相应信息到控制台,然后使用TaskManager对象的cancelTasks()方法取消所有任务,并且返回找到的元素在数组中的位置:
for (int i=start; i<end; i++){ if (numbers[i]==number) { System.out.printf("Task: Number %d found in position %d\n",number,i); manager.cancelTasks(this); return i; }
-
在循环中,设置任务休眠一秒钟:
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }
-
最后,返回-1:
return NOT_FOUND; }
-
实现launchTasks()方法。首先,将任务需要处理的数字集合分成两部分,创建两个Task任务分别处理:
private int launchTasks() { int mid=(start+end)/2; SearchNumberTask task1=new SearchNumberTask(numbers,start,mid,number,manager); SearchNumberTask task2=new SearchNumberTask(numbers,mid,end,number,manager);
-
添加任务到TaskManage对象中:
manager.addTask(task1); manager.addTask(task2);
-
使用fork()方法异步执行这两个任务:
task1.fork(); task2.fork();
-
等待任务结束,如果第一个任务结果不等于-1则返回第一个任务结果,否则返回第二个任务结果:
int returnValue; returnValue=task1.join(); if (returnValue!=-1) { return returnValue; } returnValue=task2.join(); return returnValue; }
-
实现writeCancelMessage()方法,当任务被取消时输出信息到控制台:
public void logCancelMessage(){ System.out.printf("Task: Canceled task from %d to %d\n",start,end); }
-
实现范例的主方法,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
使用ArrayGenerator创建包含1000个数字的数组:
ArrayGenerator generator=new ArrayGenerator(); int array[]=generator.generateArray(1000);
-
创建TaskManager对象:
TaskManager manager=new TaskManager();
-
使用默认构造函数创建ForkJoinPool对象:
ForkJoinPool pool=new ForkJoinPool();
-
创建Task对象来出来之前生成的数组:
SearchNumberTask task=new SearchNumberTask (array,0,1000, 5,manager);
-
使用execute()方法在线程池中异步处理任务:
pool.execute(task);
-
使用shutdown()方法关闭线程池:
pool.shutdown();
-
使用ForkJoinPool类的awaitTermination()方法等待任务结束:
try { pool.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { e.printStackTrace(); }
-
输出指明程序结束的信息到控制台:
System.out.printf("Main: The program has finished\n"); }
工作原理
如果任务还没有被执行的话,ForkJoinTask类提供cancel()方法来取消此任务。这一点很重要,如果任务已经开始执行,调用cancel()方法则无效。此方法接收名为mayInterruptIfRunning的布尔值作为参数。通过名称能够看出,如果此方法传true值,任务即便正在运行也被取消。Java API文档中指出,在ForkJoinTask类的默认实现中,此属性无效。任务只有在还未开始执行的情况下能被取消,取消操作对已取消任务发送到线程池的任务无效,它们将继续执行。
fork/join框架的局限性是不允许在ForkJoinPool中的所有任务取消,为了解决这个问题,需要实现TaskManager类,存储已经被送到线程池中的所有任务,类中有取消已经存储的所有任务的方法。如果任务因为正在运行或者已经完成而无法取消,cancel()方法返回false值,所以可以尝试取消所有任务,不用担心可能出现的副作用。
本范例中,实现了在数字数组中寻找数字的任务,用fork/join框架推荐的方式将问题拆分成子任务。只需要检索数字出现一次,所以当找到时,就取消其它任务。
下图显示本范例在控制台输出的部分执行信息:
更多关注
- 本章“创建fork/join池”小节