连接任务结果

Java 9并发编程指南 目录

fork/join框架能够执行返回结果的任务,这种任务由RecursiveTask类实现,此类继承ForkJoinTask类并且实现由执行器框架提供的Future接口。

在任务中,需要使用Java API文档推荐的程序结构:

if (problem size > size){
    tasks=Divide(task);
    execute(tasks);
    joinResults()
    return result;
} else {
    resolve problem;
    return result;
}

如果任务需要解决的问题规模比预先定义的大,则将问题拆分成很多子任务,并且使用fork/join框架执行这些子任务。当完成执行时,初始任务得到所有子任务生成的结果,将它们进行分组,并返回最终结果。最后,当初始任务在线程池中完成执行时,能够高效的得到整个问题的最终结果。

在本节中,将通过开发在文档中检索指定词的应用来学习如何使用fork/join框架来解决这种问题。本范例中需要实现如下两种任务:

  • 文件任务,用来在文档的行集合中寻找指定词
  • 行任务,用来在文档特定部分中寻找指定词

所有任务将返回在处理的部分文档或行中指定词出现的次数。本节将使用Java并发API提供的默认fork/join池。

实现过程

通过如下步骤实现范例:

  1. 创建名为DocumentMock的类,生成一个字符串矩阵来模拟文件:

    public class DocumentMock {
    
  2. 用一些词语创建字符串数组,此数组将在字符串矩阵生成中用到:

    	private String words[] = {"the", "hello", "goodbye", "packet", "java", "thread", "pool", "random", "class", "main"};
    
  3. 实现generateDocument()方法,接收三个参数,分别是行数,每行的字数,以及本范例中将要检索的词语,返回一个字符串矩阵:

    	public String[][] generateDocument(int numLines, int numWords, String word){
    
  4. 首先,创建必要的对象来生成文件–String矩阵和Random对象来生成随机数:

    		int counter = 0 ;
    		String document[][] = new String[numLines][numWords];
    		Random random = new Random();
    
  5. 向数组中填充字符串,将给定的词语数组中随机位置上的元素存储到定义的字符串矩阵中,并且在生成的数组中计算检索指定词出现的次数。通过这个结果来检查范例是否运行正常:

    		for(int i = 0 ; i < numLines ; i ++){
    			for (int j = 0 ; j < numWords ; j ++) {
    				int index = random.nextInt(words.length);
    				document[i][j] = words[index];
    				if(document[i][j].equals(word)) {
    					counter ++;
    				}
    			}
    		}
    
  6. 输出检索词出现的次数到控制台,并返回生成的矩阵:

    		System.out.printf("DocumentMock : The word --"+ word+" -- appears " + counter + " times in the document\n");
    		return document;
    	}
    
  7. 创建名为DocumentTask的类,继承Integer类参数化的RecursiveTask类。用来实现在行集合中检索词出现次数的任务:

    public class DocumentTask extends RecursiveTask<Integer>{
    
  8. 定义名为document的私有String矩阵,以及两个名为start和end的私有int属性,定义名为word的私有String属性:

    	private String document[][];
    	private int start, end;
    	private String word;
    
  9. 实现类构造函数,初始化这些属性:

    	public DocumentTask(String[][] document, int start, int end, String word) {
    		this.document = document;
    		this.start = start;
    		this.end = end;
    		this.word = word;
    	}
    
  10. 实现compute()方法,如果属性end和start差小于10,此任务通过调用processLines()方法在这两个属性之间位置的行中计算检索词出现的次数:

    	@Override
    	protected Integer compute() {
    		Integer result = null;
    		if(end - start < 10){
    			result = processLines(document, start, end, word);
    
  11. 否则,将行集合分解成两个对象,创建两个新的DocumentTask对象来处理这两个行集合,并且在线程池中使用invokeAll()方法来执行它们:

    		}else {
    			int mid = (start + end) / 2;
    			DocumentTask task1 = new DocumentTask(document, start, mid, word);
    			DocumentTask task2 = new DocumentTask(document, mid, end, word);
    			invokeAll(task1, task2);
    
  12. 然后,使用groupResults()方法添加两个任务的返回值。最后,返回任务计算出的结果:

    		try {
    				result = groupResults(task1.get(), task2.get());
    			} catch (InterruptedException | ExecutionException e) {
    				e.printStackTrace();
    			}
    		}
    		return result;
    	}
    
  13. 实现processLines()方法,参数包括字符串矩阵,start属性,end属性,以及任务检索词word属性:

    	private Integer processLines(String[][] document, int start, int end, String word) {
    
  14. 对任务需要处理的每一行,创建LineTask对象来处理完整行并且将它们存储到任务列表中:

    		List<LineTask> tasks = new ArrayList<LineTask>();
    		for ( int i = start ; i < end ; i ++){
    			LineTask task = new LineTask(document[i], 0, document[i].length, word);
    			tasks.add(task);
    		}
    
  15. 使用invokeAll()方法执行列表中的所有任务:

    		invokeAll(tasks);
    
  16. 相加所有任务返回的值,并且返回结果:

    		int result = 0 ;
    		for(int i = 0 ; i < tasks.size() ; i ++) {
    			LineTask task = tasks.get(i);
    			try {
    				result = result + task.get();
    			} catch (InterruptedException | ExecutionException e) {
    				e.printStackTrace();
    			}
    		}
    		return result;
    	}
    
  17. 实现groupResults()方法,两个数相加并返回结果:

    	private Integer groupResults(Integer number1, Integer number2) {
    		Integer result ; 
    		result = number1 + number2;
    		return result;
    	}
    
  18. 创建名为LineTask的类,继承Integer类参数化的RecursiveTask类。此类实现在一行中计算检索词出现次数的任务:

    public class LineTask extends RecursiveTask<Integer>{
    
  19. 定义名为line的私有String数组属性,和两个名为start和end的私有int属性。最后,定义名为word的私有String属性:

    	private String line[];
    	private int start, end;
    	private String word;
    
  20. 实现类构造函数,初始化这些属性:

    	public LineTask(String[] line, int start, int end, String word) {
    		this.line = line;
    		this.start = start;
    		this.end = end;
    		this.word = word;
    	}
    
  21. 实现compute()方法,如果属性end和start差小于100,此任务通过调用count()方法在这两个属性之间位置的行中寻找检索词:

    	@Override
    	protected Integer compute() {
    		Integer result = null;
    		if(end - start < 100) {
    			result = count(line, start, end, word);
    
  22. 否则,将行中的词语分成两组,创建两个LineTask对象来处理这两组词语,并且在线程池中使用invokeAll()方法来执行它们:

    		}else{
    			int mid = (start + end) / 2;
    			LineTask task1 = new LineTask(line, start, mid, word);
    			LineTask task2 = new LineTask(line, mid, end, word);
    			invokeAll(task1, task2);
    
  23. 然后,使用groupResults()方法添加两个任务的返回值。最后,返回任务计算出的结果:

    		try {
    				result = groupResults(task1.get(), task2.get());
    			} catch (InterruptedException | ExecutionException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		return result;
    	}
    
  24. 实现count()方法,参数包括字符串数组,start属性,end属性,以及任务检索词word属性:

    	private Integer count(String[] line, int start, int end, String word) {
    
  25. 将字符串数组中在start和end属性位置之间存储的词语与检索词word属性比较,如果相等的话,增加counter变量值:

            int counter = 0;
            for(int i = start ; i < end ; i ++){
                if(line[i].equals(word)){
                    counter ++;
                }
            }
    
  26. 设置任务休眠10毫秒来减慢范例执行:

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
  27. 返回couter变量值:

    		return counter;
    	}
    
  28. 实现groupResults()方法,两个数相加并返回结果:

    	private Integer groupResults(Integer number1, Integer number2) {
    		Integer result;
    		result = number1 + number2;
    		return result;
    	}
    
  29. 实现范例的主方法,创建一个包含main()方法的Main类:

    public class Main {
    	public static void main(String[] args) {
    
  30. 使用DocumentMock类创建100行每行1000词的Document对象:

    		DocumentMock mock = new DocumentMock();
    		String word = "java";
    		String[][] document = mock.generateDocument(100, 1000, word);
    
  31. 创建DocumentTask对象更新整个文件的产品,start参数赋值0,end参数赋值100:

    		DocumentTask task = new DocumentTask(document, 0, 100, word);
    
  32. 使用commonPool()得到默认的ForkJoinPool执行器,然后使用execute()方法在此执行器中运行任务:

    		ForkJoinPool commonPool = ForkJoinPool.commonPool();
    		commonPool.execute(task);
    
  33. 实现展示线程池处理进度信息的代码块,在任务完成执行之前,每隔一秒输出线程池的参数值到控制台:

    		do{
    			System.out.printf("************************************\n");
    			System.out.printf("Main : Active Threads : %d\n", commonPool.getActiveThreadCount());
    			System.out.printf("Main : Task Count : %d\n", commonPool.getQueuedTaskCount());
    			System.out.printf("Main : Steal Count : %d\n", commonPool.getStealCount());
    			System.out.printf("************************************\n");
    			
    			try {
    				TimeUnit.SECONDS.sleep(1);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}while (!task.isDone());
    
  34. 使用shutdown()方法关闭线程池:

    		commonPool.shutdown();
    
  35. 使用awaitTermination()方法等待任务结束:

    		try {
    			commonPool.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  36. 输出文件中检索词出现的次数到控制台,检查此数字与通过DocumentMock类写入的数字是否相同:

    		try {
    			System.out.printf("Main : The word  --"+ word+" --  appears %d in the document", task.get());
    		} catch (InterruptedException | ExecutionException e) {
    			e.printStackTrace();
    		}
    	}
    

工作原理

本范例中,实现两个不同的任务:

  • DocumentTask:此类的一个任务需要处理文件中由start和end属性确定的行集合。如果行集合长度小于10,则为每行创建LineTask,当所有任务结束执行时,将这些任务的结果相加并返回求和结果。如果任务需要处理的行集长度大于等于10,则拆分成两个集合,并且创建两个DocumentTask对象分别处理这两个新的集合。当这些任务结束执行时,将各自结果相加并返回求和结果。
  • LineTask:此类的一个任务需要处理文件中指定行的一组词。如果数量小于100,任务直接检索这组词并返回检索词的出现次数。否则,拆分成两组词并且创建两个LineTask对象分别处理这两个集合。当这些任务结束执行时,将各自结果相加并返回求和结果。

在Main类中,用到默认的ForkJoinPool并且在其中执行DocumentTask类来处理一个包含100行,每行1000个词的文件。此任务将问题拆分成其它的DocumentTask对象和LineTask对象,当所有任务结束执行时,使用初始任务得到整个文件中检索词出现的总次数。由于这些任务返回了结果,所以继承RecursiveTask类。

为了获得通过Task返回的结果,用到了get()方法,此方法在RecursiveTask类实现的Future接口中定义。

当执行本范例时,可以比较输出控制台第一行和最后一行的信息。第一行显示文件生成时计算出的检索词出现次数,最后一行显示通过fork/join任务计算出的相同结果。

扩展学习

complete()方法是ForkJoinTask类提供的另一种用来结束任务执行并返回结果的方法。此方法接受一个在RecursiveTask类参数化中用到的类型对象,并且当调用join()方法时,将这个对象作为任务结果返回。建议在异步认为中用此方法提供结果。

因为RecursiveTask类实现了Future接口,所以get()方法还有另一种形式:

  • get(long timeout, TimeUnit unit):在这个方法中,如果任务的结果无效,则等待指定的时间,如果已过指定时间且结果依然无效,此方法返回null值。TimeUnit是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。

更多关注

  • 本章“创建fork/join池”小节

  • 第九章“测试并发应用”中的“监控fork/join池”小节。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值