定制在fork/join框架中运行的任务

Java 9并发编程指南 目录

定制在fork/join框架中运行的任务

Executor框架将任务创建和执行分开,使用此框架,只需要实现Runnable对象和使用Executor对象。只要把Runnable任务发送给执行器,它就会创建、管理和结束必要的线程来执行这些任务。

Java 9在fork/join框架(Java 7中引入)中提供一种特殊的执行器,这个框架是用来解决如何使用分治技术将问题分解成更小的任务而设计的。在任务中,检查想要解决的问题的规模,如果问题规模大于设置的大小,则将问题划分为两个或多个任务,并使用此框架执行它们。如果问题规模小于设置的大小,则在任务中直接解决问题,且可选择的返回结果。fork/join框架实现了工作窃取算法,用来改善这类问题的整体性能。

fork/join框架的主类是ForkJoinPool类,在内部有如下两个元素:

  • 等待被执行的任务队列
  • 执行任务的线程池

默认情况下,ForkJoinPool类执行的任务是ForkJoinTask类的对象。也可以发送Runnable和Callable对象到ForkJoinPool类,但它们不能充分利用fork/join框架的所有优点。通常,把forkjoinask类的两个子类中的一个发送到ForkJoinPool对象:

  • RecursiveAction:如果任务不返回结果
  • RecursiveTask:如果任务返回结果

本节将通过实现继承ForkJoinTask类的任务为fork/join框架实现自定义的任务。将要实现的任务度量其执行时间并将结果输出到控制台,以便控制其运行进度。还可以实现自定义fork/join任务,输出日志信息来获取任务中使用的资源,或者后处理任务结果。

实现过程

通过如下步骤实现范例:

  1. 创建名为MyWorkerTask的类,指定其继承Void类型参数化的ForkJoinTask类:

    public class MyWorkerThread extends ForkJoinWorkerThread{
    
  2. 定义名为name的私有String属性,存储任务的名称:

    	private final static ThreadLocal<Integer> taskCounter= new ThreadLocal<Integer>();
    
  3. 实现类构造函数,初始化属性:

    	protected MyWorkerThread(ForkJoinPool pool) {
    		super(pool);
    	}
    
  4. 实现getRawResult()方法,这是ForkJoinTask类的一个抽象方法。由于MyWorkerTask任务不会返回任何结果,所以此方法必须返回null:

    	@Override
    	public Void getRawResult() {
    		return null;
    	}
    
  5. 实现setRawResult()方法,这是ForkJoinTask类的另一个抽象方法。由于MyWorkerTask任务不会返回任何结果,所以不用写程序:

    	@Override
    	protected void setRawResult(Void value) {
    	}
    
  6. 实现exec()方法,这是任务的main方法。这里将任务的逻辑委托给compute()方法,计算此方法的执行时间,并输出到控制台:

    	@Override
    	protected boolean exec() {
    		Date startDate=new Date();
    		compute();
    		Date finishDate=new Date();
    		long diff=finishDate.getTime()-startDate.getTime();
    		System.out.printf("MyWorkerTask: %s : %d Milliseconds to complete.\n",name,diff);
    		return true;
    	}
    
  7. 实现getName()方法,返回任务名称:

    	public String getName(){
    		return name;
    	}
    
  8. 声明抽象方法compute(),如前所述,此方法将实现任务逻辑,且必须通过MyWorkerTask类的子类来实现:

    	protected abstract void compute();
    }
    
  9. 创建名为Task的类,继承MyWorkerTask类:

    public class Task extends MyWorkerTask{
    
  10. 声明名为array的私有int类型数组:

    	private int array[];
    	private int start, end;
    
  11. 实现类构造函数,初始化属性:

    	public Task(String name, int array[], int start, int end){
    		super(name);
    		this.array=array;
    		this.start=start;
    		this.end=end;
    	}
    
  12. 实现compute()方法,此方法增加数组中由开始和结束属性决定的元素块。如果元素块中超过100个元素,则将元素块分成两个部分,并创建两个Task对象来处理每个部分。是一个invokeAll()方法发送任务到线程池:

    	@Override
    	protected void compute() {
    		if (end-start>100){
    			int mid=(end+start)/2;
    			Task task1=new Task(this.getName()+"1",array,start,mid);
    			Task task2=new Task(this.getName()+"2",array,mid,end);
    			invokeAll(task1,task2);
    
  13. 如果元素块中少于100个元素,使用for循环递增所有元素:

    		} else {
    			for (int i=start; i<end; i++) {
    				array[i]++;
    			}
    
  14. 最后,设置执行任务的线程休眠50毫秒:

    			try {
    				Thread.sleep(50);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
  15. 接下来,通过创建名为Main的类,添加main()方法,实现本范例主类:

    public class Main {
    	public static void main(String[] args)  throws Exception {
    
  16. 创建包含10000个元素的int数组:

    		int array[]=new int[10000];
    
  17. 创建名为pool的ForkJoinPool对象:

    		ForkJoinPool pool=new ForkJoinPool();
    
  18. 创建Task对象增加数组中所有元素,构造函数的参数被指定为任务的名称、数组对象以及0和10000的值,表示此任务需要处理整个数组:

    		Task task=new Task("Task",array,0,array.length);
    
  19. 使用execute()方法发送任务到线程池:

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

    		pool.shutdown();
    
  21. 输出指明程序结束的信息到控制台:

    		System.out.printf("Main: End of the program.\n");
    	}
    }
    

工作原理

本节实现了继承ForkJoinTask类的MyWorkerTask类,这是自定义的基类,用来实现能够在ForkJoinPool 执行器中执行的任务,并且由于这个执行器是工作窃取算法实现,能够利用executor的所有优点。此类相当于RecursiveAction和RecursiveTask类。

当继承ForkJoinTask类时,需要实现如下三个方法:

  • setRawResult():此方法用来确定任务结果,由于任务没有返回任何结果,此方法保持为空。
  • getRawResult():此方法用来返回任务结果,由于任务没有返回任务结果,此方法返回null。
  • exec():此方法实现任务逻辑,本范例中,将逻辑委托给抽象compute()方法(作为RecursiveAction和RecursiveTask类)。但在exec()方法中,计算此方法的执行时间,并输出到控制台:

最后,在范例主类中,创建10000个元素的数组、ForkJoinPool执行器和Task对象来处理整个数组。执行程序,将看到运行不同的任务是如何输出它们的执行时间到控制台。

更多关注

  • 第五章“Fork/Join框架”中的“创建fork/join池”小节

猜你喜欢

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