ForkJoin框架的RecursiveTask和ForkJoinPool的使用案例
ForkJoin框架是jdk7產生的一個新的併發框架,從其名字得知兩個詞fork()
拆分、join()
合併
就是利用拆分合並的思想,將一個大任務先拆分好,直到不能拆分為止,然後完成任務,最終將結果合併。
下面程式碼是計算0-1百億的和的三種計算方式。
結果是肯定超過了Long所能表示的值,但沒關係,我們只是舉個例子,結果的值不重要,只需要3個結果一致即可
先看一遍然後看後面解說
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class Demo {
public static void main(String[] args) {
long max=100_0000_0000L; //計算0到這個值的和
// 方式一,直接for暴力算
long start = System.currentTimeMillis();
long result = 0;
for (long i = 0; i <= max; i++) {
result += i;
}
long end = System.currentTimeMillis();
System.out.println("耗時:" + (end - start) + "毫秒,結果是" + result);
// 方式二。forkjoin
long start2 = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate forkJoinCalculate = new ForkJoinCalculate(0L, max);
Long result2 = pool.invoke(forkJoinCalculate);
long end2 = System.currentTimeMillis();
System.out.println("耗時:" + (end2 - start2) + "毫秒,結果是" + result2);
// 方式三。java8利用流的計算
long start3 = System.currentTimeMillis();
LongStream longStream = LongStream.rangeClosed(0, max);
long result3 = longStream.parallel().sum();
long end3 = System.currentTimeMillis();
System.out.println("耗時:" + (end3 - start3) + "毫秒,結果是" + result3);
}
}
class ForkJoinCalculate extends RecursiveTask<Long> {
private Long start;
private Long end;
private static Long THRESHOLD = 10000L;
public ForkJoinCalculate(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
Long length = end - start;
if (length <= THRESHOLD) {
long result = 0;
for (long i = start; i <= end; i++) {
result += i;
}
return result;
} else {
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork();
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
執行main得到的結果如下
耗時:3355毫秒,結果是-5340232226128654848
耗時:1566毫秒,結果是-5340232216128654848
耗時:1041毫秒,結果是-5340232216128654848
從這個結果來看,三種方式結果是一樣的因此都沒有丟值。
方式一、採用單執行緒的for直接算得到這個就不用我說什麼。所有耗時都在計算,但他是單執行緒的
方式三、採用java8的並行流得到結果
方式二、採用RecursiveTask
(遞迴任務的意思),和ForkJoinPool
。初次看來是java8的並行流效率更高,但我們調整一下引數THRESHOLD
的值看能否超過java8,方式二耗時的地方有兩塊,一是拆分任務需要時間,二計算小任務需要時間。
我將值改成了THRESHOLD = 10_0000_0000L
,結果發現耗時超過了java8的並行流
第一次執行結果:
耗時:3369毫秒,結果是-5340232226128654848
耗時:835毫秒,結果是-5340232216128654848
耗時:1095毫秒,結果是-5340232216128654848
第二次執行結果
耗時:3369毫秒,結果是-5340232226128654848
耗時:1066毫秒,結果是-5340232216128654848
耗時:1057毫秒,結果是-5340232216128654848
第三次執行結果
耗時:3362毫秒,結果是-5340232226128654848
耗時:833毫秒,結果是-5340232216128654848
耗時:1057毫秒,結果是-5340232216128654848
第四次執行結果
耗時:3369毫秒,結果是-5340232226128654848
耗時:829毫秒,結果是-5340232216128654848
耗時:1052毫秒,結果是-5340232216128654848
取了那麼多次結果會發現ForkJoinPool的效率比java8的並行流效率高,如果適當的調整應該可以得到一個最佳的效果。
以上是關於ForkJoinPool的優點下面談談缺點
我們將max的值調小一點,適當的調整THRESHOLD值
,看下那種效率高
如下一組值
long max = 1_0000_0000L;
private static Long THRESHOLD = 1_0000L;
執行的結果如下
第一次:
耗時:36毫秒,結果是5000000050000000
耗時:64毫秒,結果是5000000050000000
耗時:32毫秒,結果是5000000050000000
第二次:
耗時:36毫秒,結果是5000000050000000
耗時:52毫秒,結果是5000000050000000
耗時:30毫秒,結果是5000000050000000
第三次:
耗時:38毫秒,結果是5000000050000000
耗時:55毫秒,結果是5000000050000000
耗時:26毫秒,結果是5000000050000000
會發現java8的效率最高,forkjoin最差,原因很簡單,forkjoin拆分任務需要時間,如果拆的更細,那麼拆分的耗時也就會更大
經過分析,forkjoin它能處理一些重複,並且量很大的任務,利用拆分合並的思想將大任務化小,通過適當的調整任務的最小粒度,可以優化程式碼的執行效率。
根據上面的案例,會發現計算小的數例子中java8的並行流計算效率最佳。二計算大的數用forkjoin效率最佳。
綜合考慮
關於計算0到1百億的和,可以考慮forkjoin + java8的並行流,也許會得到更好的結果值。也就是將for迴圈改成並行流
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class Demo {
public static void main(String[] args) {
long max=100_0000_0000L;
// 方式一,直接for暴力算
long start = System.currentTimeMillis();
long result = 0;
for (long i = 0; i <= max; i++) {
result += i;
}
long end = System.currentTimeMillis();
System.out.println("耗時:" + (end - start) + "毫秒,結果是" + result);
// 方式二。forkjoin
long start2 = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinCalculate forkJoinCalculate = new ForkJoinCalculate(0L, max);
Long result2 = pool.invoke(forkJoinCalculate);
long end2 = System.currentTimeMillis();
System.out.println("耗時:" + (end2 - start2) + "毫秒,結果是" + result2);
// 方式三。java8利用流的計算
long start3 = System.currentTimeMillis();
LongStream longStream = LongStream.rangeClosed(0, max);
long result3 = longStream.parallel().sum();
long end3 = System.currentTimeMillis();
System.out.println("耗時:" + (end3 - start3) + "毫秒,結果是" + result3);
}
}
class ForkJoinCalculate extends RecursiveTask<Long> {
private Long start;
private Long end;
private static Long THRESHOLD = 10_0000_0000L;
public ForkJoinCalculate(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
Long length = end - start;
if (length <= THRESHOLD) {
LongStream longStream = LongStream.rangeClosed(start, end);
return longStream.parallel().sum();// java8並行流計算
} else {
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork();
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
在我的實際測試過程中,發現這種做法並沒有想象中的快,主要是不太穩,值有些變化,而java8的並行流計算比較穩,始終都在900-1000毫秒左右,而forkjoin好的時候是770壞的時候1600,一般在900-1000左右,還沒有前面forkjoin+for
調整條件值得800多好。
因此像這種值得計算推薦使用java8的並行流計算比較穩妥,如果是其它類,則需要權衡一下到底要不要使用forkjoin,因為用的不好反而降低效率。
上面的案例是利用了有返回值的抽象類,實際還可以使用RecursiveAction
,RecursiveAction
與RecursiveTask
區別在於實現方法compute
有無返回值
相關文章
- java8之ForkJoin框架的使用Java框架
- ForkJoin框架框架
- 併發程式設計:DEMO:比較Stream和forkjoin框架的效率程式設計框架
- 使用框架和不使用框架的區別框架
- ForkJoinPool在生產環境中使用遇到的一個問題
- ForkJoin和氣泡排序組合實現的歸併排序排序
- 使用jquery和使用框架的區別jQuery框架
- Java併發的四種風味:Thread、Executor、ForkJoin和ActorJavathread
- 集合框架-HashMap集合的案例框架HashMap
- Java併發程式設計ForkJoin的DemoJava程式設計
- 使用vert.x angular.js 結合 Axon框架的案例AngularJS框架
- Mybatis和其他主流框架的整合使用MyBatis框架
- 集合框架-LinkedHashMap的概述和使用框架HashMap
- 集合框架-LinkedHashSet的概述和使用框架
- Goroutine 和 Channel 的的使用和一些坑以及案例分析Go
- 基於Axon框架使用Kotlin編寫的ES銀行案例框架Kotlin
- 集合框架-泛型方法的概述和使用框架泛型
- 製造框架和使用框架是兩種不同的學問框架
- Web Woeker和Shared Worker的使用以及案例Web
- 多執行緒系列(二十一) -ForkJoin使用詳解執行緒
- Golang實現ForkJoin小文Golang
- 混合ORM 和MongoDB使用案例ORMMongoDB
- Java8的ForkJoin要比Java7快接近35%Java
- httprouter框架 (Gin使用的路由框架)HTTP框架路由
- BootStrap框架的使用boot框架
- 打造成功案例的Magento技術框架框架
- 集合框架-模擬鬥地主洗牌和發牌案例框架
- SlimPhp框架的使用(一)安裝Composer和SlimPHP框架
- Python程式和Flask框架中使用SQLAlchemy的教程PythonFlask框架SQL
- 併發程式設計之:ForkJoin程式設計
- 六問六答理解ForkJoin原理
- Scrapy框架的使用之Scrapyrt的使用框架
- elasticsearch 的 update by query 使用案例Elasticsearch
- 使用swiper做的小案例
- OpenCL中的SVM使用案例
- 如何透過ForkJoinPool和HikariCP將大型JSON檔案批次處理到MySQL?JSONMySql
- MapReduce框架-Join的使用框架
- Mock 框架 Moq 的使用Mock框架