取消任务

Java 9并发编程指南 目录

当在ForkJoinPool类中执行ForkJoinTask对象时,使用ForkJoinTask类提供的的cancel()方法,在对象开始执行之前取消。当取消任务时,需要关注如下两点:

  • ForkJoinPool类不提供任何方法来取消在线程池中运行或等待的所有任务。
  • 当取消一个任务时,不能取消这个任务已经执行的任务。

本节中,通过范例实现ForkJoinTask对象的取消。遍历数字数组,找到指定数字的第一个任务将取消剩余任务。由于fork/join框架不提供这种功能,需要实现辅助类来处理取消操作。

准备工作

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

实现过程

通过如下步骤实现范例:

  1. 创建名为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;
    	}
    }
    
  2. 创建名为TaskManager的类,用来存储在本范例ForkJoinPool中执行的所有任务。由于ForkJoinPool和ForkJoinTask类的局限性,将要使用此类取消ForkJoinPool类的所有任务:

    public class TaskManager {
    
  3. 使用参数化的ForkJoinTask类和Integer类定义名为tasks的对象列表:

    	private final ConcurrentLinkedDeque<SearchNumberTask> tasks;
    
  4. 实现类构造函数,初始化任务列表:

    	public TaskManager(){
    		tasks=new ConcurrentLinkedDeque<>();
    	}
    
  5. 实现addTask()方法,添加ForkJoinTask对象到任务列表中:

    	public void addTask(SearchNumberTask task){
    		tasks.add(task);
    	}
    
  6. 实现cancelTasks()方法,使用cancel()方法取消存储在列表中的所有ForkJoinTask对象。此方法接收ForkJoinTask对象作为参数来计划取消其它任务:

    	public void cancelTasks(SearchNumberTask cancelTask){
    		for (SearchNumberTask task :tasks) {
    			if (task!=cancelTask) {
    				task.cancel(true);
    				task.logCancelMessage();
    			}
    		}
    	}
    
  7. 实现SearchNumberTask类,继承Integer类参数化的RecursiveTask类。此类在整型数组的一部分元素中检索数字:

    public class SearchNumberTask extends RecursiveTask<Integer> {
    
  8. 定义名为numbers的私有整型数字数组:

    	private int numbers[];
    
  9. 定义两个名为start和end的私有整型属性,确定任务需要处理的数组元素集:

    	private int start, end;
    
  10. 定义名为number的私有整型属性,存储将要检索的数字:

    	private int number;
    
  11. 定义名为manager的私有TaskManager属性,使用此对象来取消所有任务:

    	private TaskManager manager;
    
  12. 定义初始值为-1的私有整型常量,当任务没有找到检索数字时,返回此常量:

    	private final static int NOT_FOUND=-1;
    
  13. 实现类构造函数,初始化属性:

    	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;
    	}
    
  14. 实现compute()方法,首先输出指明start和end属性值的信息到控制台:

    	@Override
    	protected Integer compute() {
    		System.out.println("Task: "+start+":"+end);
    
  15. 如果start和end属性相差大于10(任务需要处理超过10个数组中的元素),调用launchTasks()方法将任务工作拆分成两个子任务:

    		int ret;
    		if (end-start>10) {
    			ret=launchTasks();
    
  16. 否则,在任务需要处理的数组块中检索指定数字,调用lookForNumber()方法:

    		} else {
    			ret=lookForNumber();
    		}
    
  17. 返回任务结果:

    		return ret;
    	}
    
  18. 实现lookForNumber()方法:

    	private int lookForNumber() {
    
  19. 遍历任务需要处理的所有元素,将元素中存储的数字与检索数字比较,如果相等,输出相应信息到控制台,然后使用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;
    			}
    
  20. 在循环中,设置任务休眠一秒钟:

    			try {
    				TimeUnit.SECONDS.sleep(1);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    
  21. 最后,返回-1:

    		return NOT_FOUND;
    	}
    
  22. 实现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);
    
  23. 添加任务到TaskManage对象中:

    		manager.addTask(task1);
    		manager.addTask(task2);
    
  24. 使用fork()方法异步执行这两个任务:

    		task1.fork();
    		task2.fork();
    
  25. 等待任务结束,如果第一个任务结果不等于-1则返回第一个任务结果,否则返回第二个任务结果:

    		int returnValue;
    		returnValue=task1.join();
    		if (returnValue!=-1) {
    			return returnValue;
    		}
    		returnValue=task2.join();
    		return returnValue;
    	}
    
  26. 实现writeCancelMessage()方法,当任务被取消时输出信息到控制台:

    	public void logCancelMessage(){
    		System.out.printf("Task: Canceled task from %d to %d\n",start,end);
    	}
    
  27. 实现范例的主方法,创建一个包含main()方法的Main类:

    public class Main {
    	public static void main(String[] args) {
    
  28. 使用ArrayGenerator创建包含1000个数字的数组:

    		ArrayGenerator generator=new ArrayGenerator();
    		int array[]=generator.generateArray(1000);
    
  29. 创建TaskManager对象:

    		TaskManager manager=new TaskManager();
    
  30. 使用默认构造函数创建ForkJoinPool对象:

    		ForkJoinPool pool=new ForkJoinPool();
    
  31. 创建Task对象来出来之前生成的数组:

    		SearchNumberTask task=new SearchNumberTask (array,0,1000, 5,manager);
    
  32. 使用execute()方法在线程池中异步处理任务:

    		pool.execute(task);
    
  33. 使用shutdown()方法关闭线程池:

    		pool.shutdown();
    
  34. 使用ForkJoinPool类的awaitTermination()方法等待任务结束:

    		try {
    			pool.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  35. 输出指明程序结束的信息到控制台:

    		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框架推荐的方式将问题拆分成子任务。只需要检索数字出现一次,所以当找到时,就取消其它任务。

下图显示本范例在控制台输出的部分执行信息:

pics/05_04.jpg

更多关注

  • 本章“创建fork/join池”小节
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值