通常,当你实现一个简单的并发应用程序,你实现一些Runnable对象和相应的 Thread对象。在你的程序中,你控制这些线程的创建、执行和状态。Java 5引入了Executor和ExecutorService接口及其实现类进行了改进(比如:ThreadPoolExecutor类)。
执行者框架将任务的创建与执行分离。有了它,你只要实现Runnable对象和使用Executor对象。你提交Runnable任务给执行者,它创建、管理线程来执行这些任务。
Java 7更进一步,包括一个面向特定问题的ExecutorService接口的额外实现,它就是Fork/Join框架。
这个框架被设计用来解决可以使用分而治之技术将任务分解成更小的问题。在一个任务中,检查你想要解决问题的大小,如果它大于一个既定的大小,把它分解成更小的任务,然后用这个框架来执行。如果问题的大小是小于既定的大小,你直接在任务中解决这问题。它返回一个可选地结果。以下图总结了这个概念:
没有公式来确定问题的参数大小,所以你可以根据它的特点来确定一个任务是否可以被细分。你可以参考任务处理元素的大小和预估任务执行时间来确定子任务大小。你需要解决的问题是测试不同的参考大小来选择最好的一个。你可以将ForkJoinPool作为一种特殊的执行者来考虑。
这个框架基于以下两种操作:
fork操作:当你把任务分成更小的任务和使用这个框架执行它们。
join操作:当一个任务等待它创建的任务的结束。
Fork/Join 和Executor框架主要的区别是work-stealing算法。不像Executor框架,当一个任务正在等待它使用join操作创建的子任务的结 束时,执行这个任务的线程(工作线程)查找其他未被执行的任务并开始它的执行。通过这种方式,线程充分利用它们的运行时间,从而提高了应用程序的性能。
为实现这个目标,Fork/Join框架执行的任务有以下局限性:
任务只能使用fork()和join()操作,作为同步机制。如果使用其他同步机制,工作线程不能执行其他任务,当它们在同步操作时。比如,在Fork/Join框架中,你使任务进入睡眠,正在执行这个任务的工作线程将不会执行其他任务,在这睡眠期间内。
任务不应该执行I/O操作,如读或写数据文件。
任务不能抛出检查异常,它必须包括必要的代码来处理它们。
Fork/Join框架的核心是由以下两个类:
ForkJoinPool:它实现ExecutorService接口和work-stealing算法。它管理工作线程和提供关于任务的状态和它们执行的信息。
ForkJoinTask: 它是将在ForkJoinPool中执行的任务的基类。它提供在任务中执行fork()和join()操作的机制,并且这两个方法控制任务的状态。通常, 为了实现你的Fork/Join任务,你将实现两个子类的子类的类:RecursiveAction对于没有返回结果的任务和RecursiveTask 对于返回结果的任务。
创建一个Fork/Join池::::::::::::::::::::::::::::::::::
在这个指南中,你将学习如何使用Fork/Join框架的基本元素。它包括:
创建一个ForkJoinPool对象来执行任务。
创建一个ForkJoinPool执行的ForkJoinTask类。
你将在这个示例中使用Fork/Join框架的主要特点,如下:
你将使用默认构造器创建ForkJoinPool。
在这个任务中,你将使用Java API文档推荐的结构:
你将以一种同步方式执行任务。当一个任务执行2个或2个以上的子任务时,它将等待它们的结束。通过这种方式 ,正在执行这些任务的线程(工作线程)将会查找其他任务(尚未执行的任务)来执行,充分利用它们的执行时间。
你将要实现的任务将不会返回任何结果,所以你将使用RecursiveAction作为它们实现的基类。
你将继续实现一个任务来修改产品列表的价格。任务最初是负责更新一个队列中的所有元素。你将会使用10作为参考大小,如果一个任务必须更新超过10个元素,这些元素将被划分成两个部分,并创建两个任务来更新每个部分中的产品的价格。
1. public class Product {
2. private String name;
3. private double price;
4. public String getName() {
5. return name;
6. }
7. public void setName(String name) {
8. this.name = name;
9. }
10. public double getPrice() {
11. return price;
12. }
13. public void setPrice(double price) {
14. this.price = price;
15. }
16. }
1. public class ProductListGenerator {
2. public List<Product> generate (int size) {
3. List<Product> ret = new ArrayList<>();
4. for (int i = 0; i < size; i++) {
5. Product product = new Product();
6. product.setName("product"+i);
7. product.setPrice(10);
8. ret.add(product);
9. }
10. return ret;
11. }
12. }
1. public class Task extends RecursiveAction {
2. /**
3. * 声明类的序列版本uid,这个元素是必需的,因为RecursiveAction的父类ForkJoinTask实现了Serializable接口
4. */
5. private static final long serialVersionUID = 1L;
6. private List<Product> products;
7. private int first;
8. private int last;
9. private double increment;
10. public Task(List<Product> products, int first, int last, double increment) {
11. super();
12. this.products = products;
13. this.first = first;
14. this.last = last;
15. this.increment = increment;
16. }
17. @Override
18. protected void compute() {
19. // TODO Auto-generated method stub
20. if (last - first < 10) {
21. updatePrices();
22. }else {
23. int middle = (first + last) / 2;
24. System.out.printf("Task: Pending tasks;%s\n",getQueuedTaskCount());
25. Task t1 = new Task(products, first, middle+1, increment);
26. Task t2 = new Task(products, middle+1, last, increment);
27. invokeAll(t1,t2);
28. }
29. }
30. private void updatePrices(){
31. for (int i = first; i < last; i++) {
32. Product product = products.get(i);
33. product.setPrice(product.getPrice()*(1+increment));
34. }
35. }
36. }
1. public class Main {
2. public static void main(String[] args) {
3. ProductListGenerator generator = new ProductListGenerator();
4. List<Product> products = generator.generate(10000);
5. Task task = new Task(products, 0, products.size(), 0.20);
6. ForkJoinPool pool = new ForkJoinPool();
7. pool.execute(task);
8. do {
9. System.out.printf("Main: Thread Count: %d\n",pool.getActiveThreadCount());
10. System.out.printf("Main: Thread Steal: %d\n",pool.getStealCount());
11. System.out.printf("Main: Parallelism: %d\n",pool.getParallelism());
12. try {
13. TimeUnit.MILLISECONDS.sleep(5);
14. } catch (InterruptedException e) {
15. e.printStackTrace();
16. }
17. } while (!task.isDone());
18. pool.shutdown();
19. if (task.isCompletedNormally()) {
20. System.out.println("main: the process has completed normally");
21. }
22. }
23. }在这个示例中,你已经创建一个ForkJoinPool对象和一个在池中执行的ForkJoinTask类的子类。为了创建ForkJoinPool对象,你已经使用了无参构造器,所以它会以默认的配置来执行。它创建一个线程数等于计算机处理器数的池。当ForkJoinPool对象被创建时,这些线程被创建并且在池中等待,直到有任务到达让它们执行。
由于Task类没有返回结果,所以它继承RecursiveAction类。在这个指南中,你已经使用了推荐的结构来实现任务。如果这个任务更新超过10产品,它将被分解成两部分,并创建两个任务,一个任务执行一部分。你已经在Task类中使用first和last属性,用来了解这个任务要更新的产品队列的位置范围。你已经使用first和last属性,只复制产品数列一次,而不是为每个任务创建不同的数列。
它调用invokeAll()方法,执行每个任务所创建的子任务。这是一个同步调用,这个任务在继续(可能完成)它的执行之前,必须等待子任务的结束。当任务正在等待它的子任务(结束)时,正在执行它的工作线程执行其他正在等待的任务。在这种行为下,Fork/Join框架比Runnable和Callable对象本身提供一种更高效的任务管理。
ForkJoinTask类的invokeAll()方法是执行者(Executor)和Fork/Join框架的一个主要区别。在执行者框架中,所有任务被提交给执行者,而在这种情况下,这些任务包括执行和控制这些任务的方法都在池内。你已经在Task类中使用invokeAll()方法,它是继承了继承ForkJoinTask类的RecursiveAction类。
你使用execute()方法提交唯一的任务给这个池,用来所有产品数列。在这种情况下,它是一个异步调用,而主线程继续它的执行。
ForkJoinPool类提供其他的方法,用来执行一个任务。这些方法如下:
execute (Runnable task):这是在这个示例中,使用的execute()方法的另一个版本。在这种情况下,你可以提交一个Runnable对象给ForkJoinPool类。注意:ForkJoinPool类不会对Runnable对象使用work-stealing算法。它(work-stealing算法)只用于ForkJoinTask对象。
invoke(ForkJoinTask<T> task):当execute()方法使用一个异步调用ForkJoinPool类,正如你在本示例中所学的,invoke()方法使用同步调用ForkJoinPool类。这个调用不会(立即)返回,直到传递的参数任务完成它的执行。
你也可以使用在ExecutorService接口的invokeAll()和invokeAny()方法。这些方法接收一个Callable对象作为参数。ForkJoinPool类不会对Callable对象使用work-stealing算法,所以你最好使用执行者去执行它们。
ForkJoinTask类同样提供在示例中使用的invokeAll()的其他版本。这些版本如下:
invokeAll(ForkJoinTask<?>… tasks):这个版本的方法使用一个可变参数列表。你可以传入许多你想要执行的ForkJoinTask对象作为参数。
invokeAll(Collection<T> tasks):这个版本的方法接收一个泛型类型T对象的集合(如:一个ArrayList对象,一个LinkedList对象或者一个TreeSet对象)。这个泛型类型T必须是ForkJoinTask类或它的子类。
即使ForkJoinPool类被设计成用来执行一个ForkJoinTask,你也可以直接执行Runnable和Callable对象。你也可以使用ForkJoinTask类的adapt()方法来执行任务,它接收一个Callable对象或Runnable对象(作为参数)并返回一个ForkJoinTask对象。
加入任务的结果:::::::::
Fork/Join框架提供了执行返回一个结果的任务的能力。这些任务的类型是实现了RecursiveTask类。这个类继承了ForkJoinTask类和实现了执行者框架提供的Future接口。
如果这个任务必须解决一个超过预定义大小的问题,你应该将这个任务分解成更多的子任务,并且用Fork/Join框架来执行这些子任务。当这些子任务完成执行,发起的任务将获得所有子任务产生的结果 ,对这些结果进行分组,并返回最终的结果。最终,当在池中执行的发起的任务完成它的执行,你将获取整个问题地最终结果。
在这个指南中,你将学习如何使用Fork/Join框架解决这种问题,开发一个在文档中查找单词的应用程序。你将会实现以下两种任务类型:
一个文档任务,将在文档中的行集合中查找一个单词。
一个行任务,将在文档的一部分数据中查找一个单词。
所有任务将返回单词在文档的一部分中或行中出现的次数。
1. public class Document {
2. private String words[] = {"the","hello","goodbye","packt","java","thread","pool","random","class","main"};
3. public String[][] generateDocument(int numLines,int numWords,String word){
4. int counter = 0;
5. String document[][] = new String[numLines][numWords];
6. Random random = new Random();
7. for (int i = 0; i < numLines; i++) {
8. for (int j = 0; j < numWords; j++) {
9. int index = random.nextInt(words.length);
10. document[i][j] = words[index];
11. if (document[i][j].equals(word)) {
12. counter++;
13. }
14. }
15. }
16. System.out.println("DocumentMock: The word appears "+counter+" times in the document");
17. return document;
18. }
19. }
1. public class DocumentTask extends RecursiveTask<Integer>{
2. private String[][] document;
3. private int start,end;
4. private String word;
5. public DocumentTask(String[][] document, int start, int end, String word) {
6. super();
7. this.document = document;
8. this.start = start;
9. this.end = end;
10. this.word = word;
11. }
12. @Override
13. protected Integer compute() {
14. // TODO Auto-generated method stub
15. int result=0;
16. if (end - start < 10) {
17. result = processLines(document,start,end,word);
18. }else {
19. int mid = (start + end) / 2;
20. DocumentTask task1 = new DocumentTask(document, start, mid, word);
21. DocumentTask task2 = new DocumentTask(document, mid, end, word);
22. invokeAll(task1, task2);
23. try {
24. result = task1.get() + task2.get();
25. } catch (InterruptedException | ExecutionException e) {
26. // TODO Auto-generated catch block
27. e.printStackTrace();
28. }
29. }
30. return result;
31. }
32. private int processLines(String[][] document, int start, int end, String word) {
33. // TODO Auto-generated method stub
34. List<LineTask> tasks = new ArrayList<>();
35. for (int i = start; i < end; i++) {
36. LineTask task = new LineTask(document[i], 0, document[i].length, word);
37. tasks.add(task);
38. }
39. invokeAll(tasks);
40. int result = 0;
41. for (int i = 0; i < tasks.size(); i++) {
42. LineTask task = tasks.get(i);
43. try {
44. result += task.get();
45. } catch (InterruptedException | ExecutionException e) {
46. // TODO Auto-generated catch block
47. e.printStackTrace();
48. }
49. }
50. return result;
51. }
52. }
1. public class LineTask extends RecursiveTask<Integer>{
2. /**
3. *
4. */
5. private static final long serialVersionUID = 1L;
6. private String line[];
7. private int start,end;
8. private String word;
9. public LineTask(String[] line, int start, int end, String word) {
10. super();
11. this.line = line;
12. this.start = start;
13. this.end = end;
14. this.word = word;
15. }
16. @Override
17. protected Integer compute() {
18. Integer result = null;
19. if (end - start < 100) {
20. result = count(line,start,end,word);
21. }else {
22. int mid = (start + end) / 2;
23. LineTask task1 = new LineTask(line, start, mid, word);
24. LineTask task2 = new LineTask(line, mid, end, word);
25. invokeAll(task1,task2);
26. try {
27. result = task1.get() + task2.get();
28. } catch (InterruptedException | ExecutionException e) {
29. // TODO Auto-generated catch block
30. e.printStackTrace();
31. }
32. }
33. return result;
34. }
35. private Integer count(String[] line, int start, int end, String word) {
36. // TODO Auto-generated method stub
37. int counter = 0;
38. for (int i = start; i < end; i++) {
39. if (line[i].equals(word)) {
40. counter++;
41. }
42. }
43. try {
44. Thread.sleep(10);
45. } catch (InterruptedException e) {
46. // TODO Auto-generated catch block
47. e.printStackTrace();
48. }
49. return counter;
50. }
51. }
1. public class Main {
2. public static void main(String[] args) {
3. Document mock = new Document();
4. String[][] document = mock.generateDocument(100, 1000, "the");
5. DocumentTask documentTask = new DocumentTask(document, 0, 100, "the");
6. ForkJoinPool pool = new ForkJoinPool();
7. pool.execute(documentTask);
8. do{
9. System.out.printf("******************************************\n");
10. System.out.printf("Main: Parallelism: %d\n",pool.getParallelism());
11. System.out.printf("Main: Active Threads: %d\n",pool.getActiveThreadCount());
12. System.out.printf("Main: Task Count: %d\n",pool.getQueuedTaskCount());
13. System.out.printf("Main: Steal Count: %d\n",pool.getStealCount());
14. System.out.printf("******************************************\n");
15. try {
16. TimeUnit.SECONDS.sleep(1);
17. } catch (InterruptedException e) {
18. e.printStackTrace();
19. }
20. } while (!documentTask.isDone());
21. pool.shutdown();
22. try {
23. System.out.printf("Main: The word appears %d in the document",documentTask.get());
24. } catch (InterruptedException | ExecutionException e) {
25. // TODO Auto-generated catch block
26. e.printStackTrace();
27. }
28. }
29. }在这个示例中,你已经实现了两种不同的任务:
DocumentTask类:这个类的任务将处理由start和end属性决定的文档中的行组。如果这个行组的大小小于10,它为每行创建LineTask对象,并且当它们完成它们的执行时,它合计这些任务的结果,并返回这个合计值。如果这个任务要处理的行组大小不小于10,它将这个组分成两个并创建两个DocumentTask对象来处理这些新组。当这些任务完成它们的执行时,这个任务合计它们的结果,并返回这个合计值。
LineTask类:这个类的任务将处理文档中的一行的单词组。如果这个单词组小于10,这个任务直接在这个单词组中查找单词,并且返回这个单词出现的次数。否则,它将这个单词组分成两个并创建两个LineTask对象来处理。当这些任务完成它们的执行,这个任务合计这些任务的结果并返回这个合计值。
在Main类中,你已经使用默认构造器一个ForkJoinPool对象,并且你在它里面执行一个DocumentTask类,这个类将处理一个拥有100行,每行有1000个单词的文档。这个任务将使用其他的DocumentTask对象和LineTask对象来分解这个问题,当所有任务完成它们的执行,你可以使用启动任务来获取单词在整个文档中出现的总次数。由于任务返回一个结果,所以它们继承RecursiveTask类。
为了获取Task返回的结果,你已经使用了get()方法 。这个方法是在Future接口中声明的,由RecursiveTask类实现的。
当你执行这个程序,你可以比较在控制台中的第一行和最后一行。第一行是文档生成时计算的单词出现的次数,最后一行是由Fork/Join任务计算的。
异步运行任务::::::::::::::::::::::::::::::::::::::::::::::::::::::
当你在ForkJoinPool中执行ForkJoinTask时,你可以使用同步或异步方式来实现。当你使用同步方式时,提交任务给池的方法直到提交的任务完成它的执行,才会返回结果。当你使用异步方式时,提交任务给执行者的方法将立即返回,所以这个任务可以继续执行。
你应该意识到这两个方法有很大的区别,当你使用同步方法,调用这些方法(比如:invokeAll()方法)的任务将被阻塞,直到提交给池的任务完成它的执行。这允许ForkJoinPool类使用work-stealing算法,分配一个新的任务给正在执行睡眠任务的工作线程。反之,当你使用异步方法(比如:fork()方法),这个任务将继续它的执行,所以ForkJoinPool类不能使用work-stealing算法来提高应用程序的性能。在这种情况下,只有当你调用join()或get()方法来等待任务的完成时,ForkJoinPool才能使用work-stealing算法。
在这个指南中,你将学习如何使用ForkJoinPool和ForkJoinTask类提供的异步方法来管理任务。你将实现一个程序,在一个文件夹及其子文件夹内查找确定扩展名的文件。你将实现ForkJoinTask类来处理文件夹的内容。对于文件夹里的每个子文件夹,它将以异步的方式提交一个新的任务给ForkJoinPool类。对于文件夹里的每个文件,任务将检查文件的扩展名,如果它被处理,并把它添加到结果列表。
1. package demo29;
2. import java.io.File;
3. import java.util.ArrayList;
4. import java.util.List;
5. import java.util.concurrent.RecursiveTask;
6. public class FolderProcessor extends RecursiveTask<List<String>> {
7. private static final long serialVersionUID = 1L;
8. private String path;
9. private String extension;
10. public FolderProcessor(String path, String extension) {
11. super();
12. this.path = path;
13. this.extension = extension;
14. }
15. @Override
16. protected List<String> compute() {
17. List<String> list = new ArrayList<>();
18. List<FolderProcessor> tasks = new ArrayList<>();
19. File file = new File(path);
20. File content[] = file.listFiles();
21. if (content != null) {
22. for (int i = 0; i < content.length; i++) {
23. if (content[i].isDirectory()) {
24. FolderProcessor task = new FolderProcessor(content[i].getAbsolutePath(), extension);
25. task.fork();//使用fork方法异步地执行它
26. tasks.add(task);
27. }else {
28. if (checkFile(content[i].getName())) {
29. list.add(content[i].getAbsolutePath());
30. }
31. }
32. }
33. if (tasks.size() > 50) {
34. System.out.printf("%s: %d tasks ran.\n",file.getAbsolutePath(),tasks.size());
35. }
36. addResultsFromTasks(list,tasks);
37. }
38. return list;
39. }
40. private void addResultsFromTasks(List<String> list, List<FolderProcessor> tasks) {
41. for (FolderProcessor folderProcessor : tasks) {
42. list.addAll(folderProcessor.join());//调用join方法,这将等待任务执行的完成,并且返回任务的结果。
43. }
44. }
45. private boolean checkFile(String name) {
46. return name.endsWith(extension);
47. }
48. }
1. public class Main {
2. public static void main(String[] args) {
3. ForkJoinPool pool = new ForkJoinPool();
4. FolderProcessor system = new FolderProcessor("c:\\windows", "log");
5. FolderProcessor apps = new FolderProcessor("c:\\Program Files", "log");
6. FolderProcessor documents = new FolderProcessor("c:\\Documents And Settings", "log");
7. pool.execute(documents);
8. pool.execute(apps);
9. pool.execute(system);
10. do {
11. System.out.printf("******************************************\n");
12. System.out.printf("Main: Parallelism: %d\n",pool.
13. getParallelism());
14. System.out.printf("Main: Active Threads: %d\n",pool.
15. getActiveThreadCount());
16. System.out.printf("Main: Task Count: %d\n",pool.
17. getQueuedTaskCount());
18. System.out.printf("Main: Steal Count: %d\n",pool.
19. getStealCount());
20. System.out.printf("******************************************\n");
21. try {
22. TimeUnit.SECONDS.sleep(1);
23. } catch (InterruptedException e) {
24. e.printStackTrace();
25. }
26. } while((!system.isDone())||(!apps.isDone())||(!documents.
27. isDone()));
28. pool.shutdown();
29. List<String> results = system.join();
30. System.out.printf("System: %d files found.\n",results.size());
31. }
32. }这个例子的关键是FolderProcessor类。每个任务处理文件夹的内容。如你所知,这个内容有以下两种元素:
文件
其他文件夹
如果任务找到一个文件夹,它创建另一个Task对象来处理这个文件夹,并使用fork()方法把它(Task对象)提交给池。这个方法提交给池的任务将被执行,如果池中有空闲的工作线程或池可以创建一个新的工作线程。这个方法会立即返回,所以这个任务可以继续处理文件夹的内容。对于每个文件,任务将它的扩展与所想要查找的(扩展)进行比较,如果它们相等,将文件名添加到结果数列。
一旦这个任务处理完指定文件夹的所有内容,它将使用join()方法等待已提交到池的所有任务的结束。这个方法在一个任务等待其执行结束时调用,并返回compute()方法返回的值。这个任务将它自己发送的所有任务的结果和它自己的结果分组,并返回作为compute()方法的一个返回值的数组。
ForkJoinPool类同时允许任务的执行以异步的方式。你已经使用execute()方法,提交3个初始任务给池。在Main类中,你也使用shutdown()方法结束池,并打印关于正在池中运行任务的状态和变化的信息。ForkJoinPool类包含更多方法,可用于这个目的(异步执行任务)。参见监控一个Fork/Join池指南,看这些方法完整的列表。
在这个示例中,你已经使用了join()方法来等待任务的结束,并获得它们的结果。对于这个目的,你也可以使用get()方法的两个版本之一:
get():这个版本的get()方法,如果ForkJoinTask已经结束它的执行,则返回compute()方法的返回值,否则,等待直到它完成。
get(long timeout, TimeUnit unit):这个版本的get()方法,如果任务的结果不可用,则在指定的时间内等待它。如果超时并且任务的结果仍不可用,这个方法返回null值。TimeUnit类是一个枚举类,包含以下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS,MINUTES, NANOSECONDS 和 SECONDS。
get()和join()有两个主要的区别:
join()方法不能被中断。如果你中断调用join()方法的线程,这个方法将抛出InterruptedException异常。
如果任务抛出任何未受检异常,get()方法将返回一个ExecutionException异常,而join()方法将返回一个RuntimeException异常。
在任务中抛出异常:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
在Java中有两种异常:
已检查异常(Checked exceptions):这些异常必须在一个方法的throws从句中指定或在内部捕捉它们。比如:IOException或ClassNotFoundException。
未检查异常(Unchecked exceptions):这些异常不必指定或捕捉。比如:NumberFormatException。
在ForkJoinTask类的compute()方法中,你不能抛出任何已检查异常,因为在这个方法的实现中,它没有包含任何抛出(异常)声明。你必须包含必要的代码来处理异常。但是,你可以抛出(或者它可以被任何方法或使用内部方法的对象抛出)一个未检查异常。ForkJoinTask和ForkJoinPool类的行为与你可能的期望不同。程序不会结束执行,并且你将不会在控制台看到任何关于异常的信息。它只是被吞没,好像它没抛出(异常)。你可以使用ForkJoinTask类的一些方法,得知一个任务是否抛出异常及其异常种类。在这个指南中,你将学习如何获取这些信息。
1. public class Task extends RecursiveTask<Integer>{
2. private int array[];
3. private int start,end;
4. public Task(int[] array, int start, int end) {
5. super();
6. this.array = array;
7. this.start = start;
8. this.end = end;
9. }
10. @Override
11. protected Integer compute() {
12. System.out.printf("Task: Start from %d to %d\n",start,end);
13. if (end - start < 10) {
14. if ((3 > start) && (3 < end)) {
15. throw new RuntimeException("this task throws an exception");
16. }
17. try {
18. TimeUnit.SECONDS.sleep(1);
19. } catch (InterruptedException e) {
20. // TODO Auto-generated catch block
21. e.printStackTrace();
22. }
23. }else {
24. int mid = (start + end ) / 2;
25. Task task1 = new Task(array, start, mid);
26. Task task2 = new Task(array, mid, end);
27. invokeAll(task1,task2);
28. }
29. return 0;
30. }
31. }
1. public class Main {
2. public static void main(String[] args) {
3. int array[] = new int[100];
4. Task task = new Task(array, 0, 100);
5. ForkJoinPool pool = new ForkJoinPool();
6. pool.execute(task);
7. pool.shutdown();
8. try {
9. pool.awaitTermination(1, TimeUnit.DAYS);//等待任务结束
10. } catch (InterruptedException e) {
11. // TODO Auto-generated catch block
12. e.printStackTrace();
13. }
14. if (task.isCompletedAbnormally()) {//检查这个任务和它的子任务是否已经抛出异常
15. System.out.printf("Main: An exception has ocurred\n");
16. System.out.printf("Main: %s\n",task.getException());//获取那个异常
17. }
18. System.out.printf("Main: Result: %d",task.join());
19. }
20. }在这个指南中,你已经实现Task类来处理一个数字数组。它检查要处理的数字块是否是10个或更多的元素。在这种情况下,它将数字块分成两块,并创建两个新的Task对象来处理这些块。否则,他查找数组中的第4个位置的元素(索引号3)。如果这个元素在任务要处理的块中,它抛出一个RuntimeException异常。
当你执行这个程序,异常是抛出了,但程序并没有停止。在Main类中,你已经使用发起任务调用ForkJoinTask类的isCompletedAbnormally()方法。如果任务或它的子任务抛出异常,这个方法返回true。你同时使用了同样对象的getException()方法来获取已抛出的Exception对象。
当你在一个任务中抛出一个未检查异常时,它也影响到它的父任务(把它提交到ForkJoinPool类的任务)和父任务的父任务,以此类推。如果你修订程序的所有输出,你将会看到一些任务结束没有输出信息。这些任务的开始信息如下:
Task: Starting form 0 to 100
Task: Starting form 0 to 50
Task: Starting form 0 to 25
Task: Starting form 0 to 12
Task: Starting form 0 to 6
这些任务是那些及其父任务抛出异常的任务。它们全部异常地完成。考虑到这一点,当你使用ForkJoinPool和ForkJoinTask对象开发一个程序,当你不想这种行为时,可以抛出异常。
取消任务::::::::::::::::::::::::::::::::::::::::::::::::::::::
当你在一个ForkJoinPool类中执行ForkJoinTask对象,在它们开始执行之前,你可以取消执行它们。ForkJoinTask类提供cancel()方法用于这个目的。当你想要取消一个任务时,有一些点你必须考虑一下,这些点如下:
ForkJoinPool类并没有提供任何方法来取消正在池中运行或等待的所有任务。
当你取消一个任务时,你不能取消一个已经执行的任务。
在这个指南中,你将实现取消ForkJoinTask对象的例子。你将查找数在数组中的位置。第一个找到这个数的任务将取消剩下的任务(未找到这个数的任务)。由于Fork/Join框架并没有提供这种功能,所以,你将实现一个辅助类来做这个取消功能。
1. public class ArrayGenerator {
2. public int[] generateArray(int size){
3. int array[] = new int[size];
4. Random random = new Random();
5. for (int i = 0; i < size; i++) {
6. array[i] = random.nextInt(10);
7. }
8. return array;
9. }
10. }
1. public class TaskManager {
2. private List<ForkJoinTask<Integer>> tasks;
3. public TaskManager() {
4. tasks = new ArrayList<>();
5. }
6. public void addTask(ForkJoinTask<Integer> task){
7. tasks.add(task);
8. }
9. public void cancelTask(ForkJoinTask<Integer> cancelTask){
10. for (ForkJoinTask<Integer> task : tasks) {
11. if (task != cancelTask) {
12. task.cancel(true);//取消
13. ((SearchNumberTask)task).writeCancelMessage();
14. }
15. }
16. }
17. }
1. public class SearchNumberTask extends RecursiveTask<Integer>{
2. private int numbers[];
3. private int start,end;
4. private int number;
5. private TaskManager manager;
6. private final static int NOT_FOUND = -1;
7. public SearchNumberTask(int[] numbers, int start, int end, int number, TaskManager manager) {
8. super();
9. this.numbers = numbers;
10. this.start = start;
11. this.end = end;
12. this.number = number;
13. this.manager = manager;
14. }
15. @Override
16. protected Integer compute() {
17. System.out.println("Task: "+start+":"+end);
18. int ret;
19. if (end - start > 10) {
20. ret = lanunchTasks();
21. }else {
22. ret = lookforNumber();
23. }
24. return ret;
25. }
26. private int lanunchTasks() {
27. int mid =(start+end) / 2;
28. SearchNumberTask task1 = new SearchNumberTask(numbers, start, mid, number, manager);
29. SearchNumberTask task2 = new SearchNumberTask(numbers, mid, end, number, manager);
30. manager.addTask(task1);
31. manager.addTask(task2);
32. task1.fork();
33. task2.fork();
34. int returnValue = task1.join();
35. if (returnValue != -1) {
36. return returnValue;
37. }
38. returnValue = task2.join();
39. return returnValue;
40. }
41. private int lookforNumber() {
42. for (int i = start; i < end; i++) {
43. if (numbers[i] == number) {
44. manager.cancelTask(this);
45. return i;
46. }
47. try {
48. TimeUnit.SECONDS.sleep(1);
49. } catch (InterruptedException e) {
50. // TODO Auto-generated catch block
51. e.printStackTrace();
52. }
53. }
54. return NOT_FOUND;
55. }
56. public void writeCancelMessage() {
57. System.out.println("cancel task");
58. }
59. }
1. package demo31;
2. import java.util.concurrent.ForkJoinPool;
3. import java.util.concurrent.TimeUnit;
4. public class Main {
5. public static void main(String[] args) {
6. ArrayGenerator generator = new ArrayGenerator();
7. int array[] = generator.generateArray(1000);
8. TaskManager manager = new TaskManager();
9. ForkJoinPool pool = new ForkJoinPool();
10. SearchNumberTask task = new SearchNumberTask(array, 0, 1000, 5, manager);
11. pool.execute(task);
12. pool.shutdown();
13. try {
14. pool.awaitTermination(1, TimeUnit.DAYS);
15. } catch (InterruptedException e) {
16. // TODO Auto-generated catch block
17. e.printStackTrace();
18. }
19. }
20. }ForkJoinTask提供cancel()方法,允许你取消一个还未执行的任务。这是一个非常重要的点。如果任务已经开始它的执行,那么调用cancel()方法对它没有影响。这个方法接收一个Boolean值,名为mayInterruptIfRunning的参数。这个名字可能让你觉得,如果你传入一个true值给这个方法,这个任务将被取消,即使它正在运行。
Java API文档指出,在ForkJoinTask类的默认实现中,这个属性不起作用。任务只能在它们还未开始执行时被取消。一个任务的取消不会影响到已经提到到池的(其他)任务。它们继续它们的执行。 Fork/Join框架的一个局限性是,它不允许取消在ForkJoinPool中的所有任务。为了克服这个限制,你实现了TaskManager类。它存储被提到池中的所有任务。它有一个方法取消它存储的所有任务。如果一个任务由于它正在运行或已经完成而不能被取消,cancel()方法返回false值,所以,你可以尝试取消所有任务,而不用担心可能有间接的影响。 在这个例子中,你已经实现一个任务,用来在一个数字数组中查找一个数。如Fork/Join框架所推荐的,你将问题分解成更小的子问题。你只关心这个数的出现,所以当你找到它,你取消了其他任务。
本文介绍Fork/Join框架的基础知识,包括如何创建和使用ForkJoinPool、实现ForkJoinTask,以及如何利用该框架高效处理任务。文章通过多个示例详细展示了Fork/Join框架在实际应用中的优势。
730

被折叠的 条评论
为什么被折叠?



