Java使用Fork/Join框架來並行執行任務

現代的計算機已經向多CPU方向發展,即使是普通的PC,甚至現在的智能手機、多核處理器已被廣泛應用。在未來,處理器的核心數將會發展的越來越多。

雖然硬件上的多核CPU已經十分成熟,但是很多應用程序並未這種多核CPU做好準備,因此並不能很好地利用多核CPU的性能優勢。

爲了充分利用多CPU、多核CPU的性能優勢,級軟基軟件系統應該可以充分“挖掘”每個CPU的計算能力,決不能讓某個CPU處於“空閒”狀態。爲此,可以考慮把一個任務拆分成多個“小任務”,把多個"小任務"放到多個處理器核心上並行執行。當多個“小任務”執行完成之後,再將這些執行結果合併起來即可。

如下面的示意圖所示:


第一步分割任務。首先我們需要有一個fork類來把大任務分割成子任務,有可能子任務還是很大,所以還需要不停的分割,直到分割出的子任務足夠小。

第二步執行任務併合並結果。分割的子任務分別放在雙端隊列裏,然後幾個啓動線程分別從雙端隊列裏獲取任務執行。子任務執行完的結果都統一放在一個隊列裏,啓動一個線程從隊列裏拿數據,然後合併這些數據。


Java提供了ForkJoinPool來支持將一個任務拆分成多個“小任務”並行計算,再把多個“小任務”的結果合成總的計算結果。

ForkJoinPool是ExecutorService的實現類,因此是一種特殊的線程池。ForkJoinPool提供瞭如下兩個常用的構造器。

  •  public ForkJoinPool(int parallelism):創建一個包含parallelism個並行線程的ForkJoinPool
  •  public ForkJoinPool() :以Runtime.getRuntime().availableProcessors()的返回值作爲parallelism來創建ForkJoinPool

創建ForkJoinPool實例後,可以釣魚ForkJoinPool的submit(ForkJoinTask<T> task)或者invoke(ForkJoinTask<T> task)來執行指定任務。其中ForkJoinTask代表一個可以並行、合併的任務。ForkJoinTask是一個抽象類,它有兩個抽象子類:RecursiveAction和RecursiveTask。

  • RecursiveTask代表有返回值的任務
  • RecursiveAction代表沒有返回值的任務。


一、RecursiveAction

下面以一個沒有返回值的大任務爲例,介紹一下RecursiveAction的用法。

大任務是:打印0-200的數值。

小任務是:每次只能打印50個數值。

[java] view plaincopy
  1. import java.util.concurrent.ForkJoinPool;  
  2. import java.util.concurrent.RecursiveAction;  
  3. import java.util.concurrent.TimeUnit;  
  4.   
  5. //RecursiveAction爲ForkJoinTask的抽象子類,沒有返回值的任務  
  6. class PrintTask extends RecursiveAction {  
  7.     // 每個"小任務"最多隻打印50個數  
  8.     private static final int MAX = 50;  
  9.   
  10.     private int start;  
  11.     private int end;  
  12.   
  13.     PrintTask(int start, int end) {  
  14.         this.start = start;  
  15.         this.end = end;  
  16.     }  
  17.   
  18.     @Override  
  19.     protected void compute() {  
  20.         // 當end-start的值小於MAX時候,開始打印  
  21.         if ((end - start) < MAX) {  
  22.             for (int i = start; i < end; i++) {  
  23.                 System.out.println(Thread.currentThread().getName() + "的i值:"  
  24.                         + i);  
  25.             }  
  26.         } else {  
  27.             // 將大任務分解成兩個小任務  
  28.             int middle = (start + end) / 2;  
  29.             PrintTask left = new PrintTask(start, middle);  
  30.             PrintTask right = new PrintTask(middle, end);  
  31.             // 並行執行兩個小任務  
  32.             left.fork();  
  33.             right.fork();  
  34.         }  
  35.     }  
  36. }  
  37.   
  38. public class ForkJoinPoolTest {  
  39.     /** 
  40.      * @param args 
  41.      * @throws Exception 
  42.      */  
  43.     public static void main(String[] args) throws Exception {  
  44.         // 創建包含Runtime.getRuntime().availableProcessors()返回值作爲個數的並行線程的ForkJoinPool  
  45.         ForkJoinPool forkJoinPool = new ForkJoinPool();  
  46.         // 提交可分解的PrintTask任務  
  47.         forkJoinPool.submit(new PrintTask(0200));  
  48.         forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);//阻塞當前線程直到 ForkJoinPool 中所有的任務都執行結束  
  49.         // 關閉線程池  
  50.         forkJoinPool.shutdown();  
  51.     }  
  52.   
  53. }  

運行結果如下:

[java] view plaincopy
  1. ForkJoinPool-1-worker-2的i值:75  
  2. ForkJoinPool-1-worker-2的i值:76  
  3. ForkJoinPool-1-worker-2的i值:77  
  4. ForkJoinPool-1-worker-2的i值:78  
  5. ForkJoinPool-1-worker-2的i值:79  
  6. ForkJoinPool-1-worker-2的i值:80  
  7. ForkJoinPool-1-worker-2的i值:81  
  8. ForkJoinPool-1-worker-2的i值:82  
  9. ForkJoinPool-1-worker-2的i值:83  
  10. ForkJoinPool-1-worker-2的i值:84  
  11. ForkJoinPool-1-worker-2的i值:85  
  12. ForkJoinPool-1-worker-2的i值:86  
  13. ForkJoinPool-1-worker-2的i值:87  
  14. ForkJoinPool-1-worker-2的i值:88  
  15. ForkJoinPool-1-worker-2的i值:89  
  16. ForkJoinPool-1-worker-2的i值:90  
  17. ForkJoinPool-1-worker-2的i值:91  
  18. ForkJoinPool-1-worker-2的i值:92  
  19. ForkJoinPool-1-worker-2的i值:93  
  20. ForkJoinPool-1-worker-2的i值:94  
  21. ForkJoinPool-1-worker-2的i值:95  
  22. ForkJoinPool-1-worker-2的i值:96  
  23. ForkJoinPool-1-worker-2的i值:97  
  24. ForkJoinPool-1-worker-2的i值:98  
  25. ForkJoinPool-1-worker-2的i值:99  
  26. ForkJoinPool-1-worker-2的i值:50  
  27. ForkJoinPool-1-worker-2的i值:51  
  28. ForkJoinPool-1-worker-2的i值:52  
  29. ForkJoinPool-1-worker-2的i值:53  
  30. ForkJoinPool-1-worker-2的i值:54  
  31. ForkJoinPool-1-worker-2的i值:55  
  32. ForkJoinPool-1-worker-2的i值:56  
  33. ForkJoinPool-1-worker-2的i值:57  
  34. ForkJoinPool-1-worker-2的i值:58  
  35. ForkJoinPool-1-worker-2的i值:59  
  36. ForkJoinPool-1-worker-2的i值:60  
  37. ForkJoinPool-1-worker-2的i值:61  
  38. ForkJoinPool-1-worker-2的i值:62  
  39. ForkJoinPool-1-worker-2的i值:63  
  40. ForkJoinPool-1-worker-2的i值:64  
  41. ForkJoinPool-1-worker-2的i值:65  
  42. ForkJoinPool-1-worker-2的i值:66  
  43. ForkJoinPool-1-worker-2的i值:67  
  44. ForkJoinPool-1-worker-2的i值:68  
  45. ForkJoinPool-1-worker-2的i值:69  
  46. ForkJoinPool-1-worker-1的i值:175  
  47. ForkJoinPool-1-worker-1的i值:176  
  48. ForkJoinPool-1-worker-1的i值:177  
  49. ForkJoinPool-1-worker-1的i值:178  
  50. ForkJoinPool-1-worker-1的i值:179  
  51. ForkJoinPool-1-worker-1的i值:180  
  52. ForkJoinPool-1-worker-1的i值:181  
  53. ForkJoinPool-1-worker-1的i值:182  
  54. ForkJoinPool-1-worker-1的i值:183  
  55. ForkJoinPool-1-worker-1的i值:184  
  56. ForkJoinPool-1-worker-1的i值:185  
  57. ForkJoinPool-1-worker-1的i值:186  
  58. ForkJoinPool-1-worker-1的i值:187  
  59. ForkJoinPool-1-worker-1的i值:188  
  60. ForkJoinPool-1-worker-1的i值:189  
  61. ForkJoinPool-1-worker-1的i值:190  
  62. ForkJoinPool-1-worker-1的i值:191  
  63. ForkJoinPool-1-worker-1的i值:192  
  64. ForkJoinPool-1-worker-1的i值:193  
  65. ForkJoinPool-1-worker-1的i值:194  
  66. ForkJoinPool-1-worker-1的i值:195  
  67. ForkJoinPool-1-worker-1的i值:196  
  68. ForkJoinPool-1-worker-1的i值:197  
  69. ForkJoinPool-1-worker-1的i值:198  
  70. ForkJoinPool-1-worker-1的i值:199  
  71. ForkJoinPool-1-worker-1的i值:150  
  72. ForkJoinPool-1-worker-1的i值:151  
  73. ForkJoinPool-1-worker-1的i值:152  
  74. ForkJoinPool-1-worker-1的i值:153  
  75. ForkJoinPool-1-worker-1的i值:154  
  76. ForkJoinPool-1-worker-1的i值:155  
  77. ForkJoinPool-1-worker-1的i值:156  
  78. ForkJoinPool-1-worker-1的i值:157  
  79. ForkJoinPool-1-worker-1的i值:158  
  80. ForkJoinPool-1-worker-1的i值:159  
  81. ForkJoinPool-1-worker-1的i值:160  
  82. ForkJoinPool-1-worker-1的i值:161  
  83. ForkJoinPool-1-worker-1的i值:162  
  84. ForkJoinPool-1-worker-1的i值:163  
  85. ForkJoinPool-1-worker-1的i值:164  
  86. ForkJoinPool-1-worker-1的i值:165  
  87. ForkJoinPool-1-worker-1的i值:166  
  88. ForkJoinPool-1-worker-1的i值:167  
  89. ForkJoinPool-1-worker-1的i值:168  
  90. ForkJoinPool-1-worker-1的i值:169  
  91. ForkJoinPool-1-worker-1的i值:170  
  92. ForkJoinPool-1-worker-1的i值:171  
  93. ForkJoinPool-1-worker-1的i值:172  
  94. ForkJoinPool-1-worker-1的i值:173  
  95. ForkJoinPool-1-worker-1的i值:174  
  96. ForkJoinPool-1-worker-1的i值:125  
  97. ForkJoinPool-1-worker-1的i值:126  
  98. ForkJoinPool-1-worker-1的i值:127  
  99. ForkJoinPool-1-worker-1的i值:128  
  100. ForkJoinPool-1-worker-1的i值:129  
  101. ForkJoinPool-1-worker-1的i值:130  
  102. ForkJoinPool-1-worker-1的i值:131  
  103. ForkJoinPool-1-worker-1的i值:132  
  104. ForkJoinPool-1-worker-1的i值:133  
  105. ForkJoinPool-1-worker-1的i值:134  
  106. ForkJoinPool-1-worker-1的i值:135  
  107. ForkJoinPool-1-worker-1的i值:136  
  108. ForkJoinPool-1-worker-1的i值:137  
  109. ForkJoinPool-1-worker-1的i值:138  
  110. ForkJoinPool-1-worker-1的i值:139  
  111. ForkJoinPool-1-worker-1的i值:140  
  112. ForkJoinPool-1-worker-1的i值:141  
  113. ForkJoinPool-1-worker-1的i值:142  
  114. ForkJoinPool-1-worker-1的i值:143  
  115. ForkJoinPool-1-worker-1的i值:144  
  116. ForkJoinPool-1-worker-1的i值:145  
  117. ForkJoinPool-1-worker-1的i值:146  
  118. ForkJoinPool-1-worker-1的i值:147  
  119. ForkJoinPool-1-worker-1的i值:148  
  120. ForkJoinPool-1-worker-1的i值:149  
  121. ForkJoinPool-1-worker-1的i值:100  
  122. ForkJoinPool-1-worker-1的i值:101  
  123. ForkJoinPool-1-worker-1的i值:102  
  124. ForkJoinPool-1-worker-1的i值:103  
  125. ForkJoinPool-1-worker-1的i值:104  
  126. ForkJoinPool-1-worker-1的i值:105  
  127. ForkJoinPool-1-worker-1的i值:106  
  128. ForkJoinPool-1-worker-1的i值:107  
  129. ForkJoinPool-1-worker-1的i值:108  
  130. ForkJoinPool-1-worker-1的i值:109  
  131. ForkJoinPool-1-worker-1的i值:110  
  132. ForkJoinPool-1-worker-1的i值:111  
  133. ForkJoinPool-1-worker-1的i值:112  
  134. ForkJoinPool-1-worker-1的i值:113  
  135. ForkJoinPool-1-worker-1的i值:114  
  136. ForkJoinPool-1-worker-1的i值:115  
  137. ForkJoinPool-1-worker-1的i值:116  
  138. ForkJoinPool-1-worker-1的i值:117  
  139. ForkJoinPool-1-worker-1的i值:118  
  140. ForkJoinPool-1-worker-1的i值:119  
  141. ForkJoinPool-1-worker-1的i值:120  
  142. ForkJoinPool-1-worker-1的i值:121  
  143. ForkJoinPool-1-worker-1的i值:122  
  144. ForkJoinPool-1-worker-1的i值:123  
  145. ForkJoinPool-1-worker-1的i值:124  
  146. ForkJoinPool-1-worker-1的i值:25  
  147. ForkJoinPool-1-worker-1的i值:26  
  148. ForkJoinPool-1-worker-1的i值:27  
  149. ForkJoinPool-1-worker-1的i值:28  
  150. ForkJoinPool-1-worker-1的i值:29  
  151. ForkJoinPool-1-worker-1的i值:30  
  152. ForkJoinPool-1-worker-1的i值:31  
  153. ForkJoinPool-1-worker-1的i值:32  
  154. ForkJoinPool-1-worker-1的i值:33  
  155. ForkJoinPool-1-worker-1的i值:34  
  156. ForkJoinPool-1-worker-1的i值:35  
  157. ForkJoinPool-1-worker-1的i值:36  
  158. ForkJoinPool-1-worker-1的i值:37  
  159. ForkJoinPool-1-worker-1的i值:38  
  160. ForkJoinPool-1-worker-1的i值:39  
  161. ForkJoinPool-1-worker-1的i值:40  
  162. ForkJoinPool-1-worker-1的i值:41  
  163. ForkJoinPool-1-worker-1的i值:42  
  164. ForkJoinPool-1-worker-1的i值:43  
  165. ForkJoinPool-1-worker-1的i值:44  
  166. ForkJoinPool-1-worker-1的i值:45  
  167. ForkJoinPool-1-worker-1的i值:46  
  168. ForkJoinPool-1-worker-1的i值:47  
  169. ForkJoinPool-1-worker-1的i值:48  
  170. ForkJoinPool-1-worker-1的i值:49  
  171. ForkJoinPool-1-worker-1的i值:0  
  172. ForkJoinPool-1-worker-1的i值:1  
  173. ForkJoinPool-1-worker-1的i值:2  
  174. ForkJoinPool-1-worker-1的i值:3  
  175. ForkJoinPool-1-worker-1的i值:4  
  176. ForkJoinPool-1-worker-1的i值:5  
  177. ForkJoinPool-1-worker-1的i值:6  
  178. ForkJoinPool-1-worker-1的i值:7  
  179. ForkJoinPool-1-worker-1的i值:8  
  180. ForkJoinPool-1-worker-1的i值:9  
  181. ForkJoinPool-1-worker-1的i值:10  
  182. ForkJoinPool-1-worker-1的i值:11  
  183. ForkJoinPool-1-worker-1的i值:12  
  184. ForkJoinPool-1-worker-1的i值:13  
  185. ForkJoinPool-1-worker-1的i值:14  
  186. ForkJoinPool-1-worker-1的i值:15  
  187. ForkJoinPool-1-worker-1的i值:16  
  188. ForkJoinPool-1-worker-1的i值:17  
  189. ForkJoinPool-1-worker-1的i值:18  
  190. ForkJoinPool-1-worker-1的i值:19  
  191. ForkJoinPool-1-worker-1的i值:20  
  192. ForkJoinPool-1-worker-1的i值:21  
  193. ForkJoinPool-1-worker-1的i值:22  
  194. ForkJoinPool-1-worker-1的i值:23  
  195. ForkJoinPool-1-worker-1的i值:24  
  196. ForkJoinPool-1-worker-2的i值:70  
  197. ForkJoinPool-1-worker-2的i值:71  
  198. ForkJoinPool-1-worker-2的i值:72  
  199. ForkJoinPool-1-worker-2的i值:73  
  200. ForkJoinPool-1-worker-2的i值:74  

從上面結果來看,ForkJoinPool啓動了兩個線程來執行這個打印任務,這是因爲筆者的計算機的CPU是雙核的。不僅如此,讀者可以看到程序雖然打印了0-199這兩百個數字,但是並不是連續打印的,這是因爲程序將這個打印任務進行了分解,分解後的任務會並行執行,所以不會按順序從0打印 到199。


二、RecursiveTask

下面以一個有返回值的大任務爲例,介紹一下RecursiveTask的用法。

大任務是:計算隨機的100個數字的和。

小任務是:每次只能20個數值的和。

[java] view plaincopy
  1. import java.util.Random;  
  2. import java.util.concurrent.ForkJoinPool;  
  3. import java.util.concurrent.Future;  
  4. import java.util.concurrent.RecursiveTask;  
  5.   
  6. //RecursiveTask爲ForkJoinTask的抽象子類,有返回值的任務  
  7. class SumTask extends RecursiveTask<Integer> {  
  8.     // 每個"小任務"最多隻打印50個數  
  9.     private static final int MAX = 20;  
  10.     private int arr[];  
  11.     private int start;  
  12.     private int end;  
  13.   
  14.     SumTask(int arr[], int start, int end) {  
  15.         this.arr = arr;  
  16.         this.start = start;  
  17.         this.end = end;  
  18.     }  
  19.   
  20.     @Override  
  21.     protected Integer compute() {  
  22.         int sum = 0;  
  23.         // 當end-start的值小於MAX時候,開始打印  
  24.         if ((end - start) < MAX) {  
  25.             for (int i = start; i < end; i++) {  
  26.                 sum += arr[i];  
  27.             }  
  28.             return sum;  
  29.         } else {  
  30.             System.err.println("=====任務分解======");  
  31.             // 將大任務分解成兩個小任務  
  32.             int middle = (start + end) / 2;  
  33.             SumTask left = new SumTask(arr, start, middle);  
  34.             SumTask right = new SumTask(arr, middle, end);  
  35.             // 並行執行兩個小任務  
  36.             left.fork();  
  37.             right.fork();  
  38.             // 把兩個小任務累加的結果合併起來  
  39.             return left.join() + right.join();  
  40.         }  
  41.     }  
  42.   
  43. }  
  44.   
  45. public class ForkJoinPoolTest2 {  
  46.     /** 
  47.      * @param args 
  48.      * @throws Exception 
  49.      */  
  50.     public static void main(String[] args) throws Exception {  
  51.         int arr[] = new int[100];  
  52.         Random random = new Random();  
  53.         int total = 0;  
  54.         // 初始化100個數字元素  
  55.         for (int i = 0; i < arr.length; i++) {  
  56.             int temp = random.nextInt(100);  
  57.             // 對數組元素賦值,並將數組元素的值添加到total總和中  
  58.             total += (arr[i] = temp);  
  59.         }  
  60.         System.out.println("初始化時的總和=" + total);  
  61.         // 創建包含Runtime.getRuntime().availableProcessors()返回值作爲個數的並行線程的ForkJoinPool  
  62.         ForkJoinPool forkJoinPool = new ForkJoinPool();  
  63.         // 提交可分解的PrintTask任務  
  64.         Future<Integer> future = forkJoinPool.submit(new SumTask(arr, 0,  
  65.                 arr.length));  
  66.         System.out.println("計算出來的總和=" + future.get());  
  67.         // 關閉線程池  
  68.         forkJoinPool.shutdown();  
  69.     }  
  70.   
  71. }  

計算結果如下:
[java] view plaincopy
  1. 初始化時的總和=4283  
  2. =====任務分解======  
  3. =====任務分解======  
  4. =====任務分解======  
  5. =====任務分解======  
  6. =====任務分解======  
  7. =====任務分解======  
  8. =====任務分解======  
  9. 計算出來的總和=4283  

從上面結果來看,ForkJoinPool將任務分解了7次,程序通過SumTask計算出來的結果,和初始化數組時統計出來的總和是相等的,這表明計算結果一切正常。



讀者還參考以下文章加深對ForkJoinPool的理解:

http://www.infoq.com/cn/articles/fork-join-introduction/

http://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章