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在生產環境中使用遇到的一個問題
- 使用jquery和使用框架的區別jQuery框架
- ForkJoin和氣泡排序組合實現的歸併排序排序
- Mybatis和其他主流框架的整合使用MyBatis框架
- 基於Axon框架使用Kotlin編寫的ES銀行案例框架Kotlin
- Java併發程式設計ForkJoin的DemoJava程式設計
- Goroutine 和 Channel 的的使用和一些坑以及案例分析Go
- Web Woeker和Shared Worker的使用以及案例Web
- 多執行緒系列(二十一) -ForkJoin使用詳解執行緒
- BootStrap框架的使用boot框架
- Scrapy框架的使用之Scrapyrt的使用框架
- 打造成功案例的Magento技術框架框架
- Golang實現ForkJoin小文Golang
- Google官方Fragment頁面框架Navigation和XPage開源框架的使用對比GoFragment框架Navigation
- OpenCL中的SVM使用案例
- elasticsearch 的 update by query 使用案例Elasticsearch
- Hadoop框架:MapReduce基本原理和入門案例Hadoop框架
- 使用Golang的Gin框架和vue編寫web應用Golang框架VueWeb
- MapReduce框架-Join的使用框架
- Mock 框架 Moq 的使用Mock框架
- go語言web開發框架_Iris框架講解(六):Session的使用和控制GoWeb框架Session
- 如何透過ForkJoinPool和HikariCP將大型JSON檔案批次處理到MySQL?JSONMySql
- 核取方塊和切換按鈕的7個使用案例
- 六問六答理解ForkJoin原理
- 併發程式設計之:ForkJoin程式設計
- Scrapy框架的使用之Scrapy框架介紹框架
- Docker框架的使用系列教程(四)容器的使用Docker框架
- IO的資料集使用案例
- C# 遞迴的使用案例C#遞迴
- Vue 框架-07-迴圈指令 v-for,和模板的使用Vue框架
- 說一說V-Layout框架的原理和使用場景框架
- Spring框架訪問資料庫的兩種方式的小案例Spring框架資料庫
- 基於微服務框架Micronaut和Eventuate Tram實現分散式事務的開源案例微服務框架分散式
- gin框架post路由的使用框架路由
- User Notification Framework 框架的使用Framework框架