Fork/Join 框架主要由Fork 和 Join 兩個操作構成:
1)Fork操作主要用於對任務和數據進行劃分,一般是將一個大問題劃分爲幾個小問題,以期望能夠方便地對簡化後地問題進行處理
2)Join操作主要作用於對各個部分地運行結果進行合併,相當於程序中的一個障柵。
這兩個操作與MapReduce中的Map/Reduce操作類似,簡而言之,Fork/Join框架是一種設計模式,是一種思想,在其他編程語言中也同樣適用。
Fork/Join 框架實現了任務的定義和任務處理功能的分離,使程序員在現實並行編程的同時,可以更好的集中精力實現業務邏輯相關的內容,這大大降低了並行程序設計的難度。
在Java中,Fork/Join 框架實現了ExecutorService接口,與任何以一個ExecutorService接口的功能相同的是:Fork/Join會自動地將任務分配給線程池中地線程,並負責線程管理地相關工作。
與ExecutorService不同地是:Fork/Join使用了工作竊取算法,已經完成自身任務線程可以從其他繁忙地線程中竊取任務來執行,從而保持了線程執行過程中地負載均衡。
Fork/Join 主要用途:Fork/Join框架通常被用於解決那些可以遞歸地分解爲更小地任務地問題。該框架與負載均衡、分治(Divide and Conquer)方法及工作竊取算法(Work-steaing Algorithm)有密切地聯繫。
Fork/Join地負債均衡
負載不均衡會導致一些線程空閒,從而造成資源浪費。常見了負載不均衡地原因是分配給某個線程地任務過多,其他線程都執行完畢,但該線程仍繼續執行。
負載均衡是指在CPU處理器核上運行地線程全部以相同地繁忙程度來工作,理想情況下,所有地線程都都成同樣大小的工作量。
負載均衡有利於加快程序的執行,減少資源浪費,因此我們在設計並行程序時,也應該保證負載均衡,但有些情況的負債均衡任務是由操作系統或JVM虛擬機來完成的。
Frok/Join的分治方法
分治方法是簡化問題的一種處理方法,該方法的基本思想是將一個複雜的任務分解爲若干個小任務,然後分別解決這些小任務。
使用分治方法的步驟:
1)分解。將複雜的任務分成小任務,直到把任務分解到容易解決爲止。
2)解決。對已完成分解的小人物進行求解。
3)j結果合併。如果每個任務都有返回結果,則需要對結果進行合併。
分治法可以解決合併排序,二分搜索和矩陣相乘問題。
在使用Fork/Join框架的過程中,首先對ForkJoinTask進行分解,任務分解的數目可以根據問題的特徵以及CPU可同時處理的線程數進行設定,然後將分解的任務交給ForkJoinPool處理,最後對處理結果進行收集。
工作竊取算法:
工作竊取算法是提高程序性能,保證負債均衡的一種算法。該算法的基本思想是當程序中某些線程做完自身的工作後,去查看其他線程是否還有未處理完成的工作,如果有,則竊取一部分工作來執行,從而幫助那些未完成的
程序儘快完成他們的工作。
程序在使用該算法後,一方面,可以保證線程始終處於一種忙碌狀態,提高資源的利用率;另一方面,也可以減少其他繁忙線程的處理時間,有助於提高程序的性能。
從本質上來說,工作竊取算法是一種任務調度方法,儘量使每個線程都能處於忙碌狀態。生活中"竊取"是一個編譯詞,但在這裏卻有很積極的意義。
Fork/Join框架與Executor框架的不同之處在於Fork/Join框架採用了工作竊取算法,可以說該算法是Fork/Join框架的核心。在ForKJoinPool中,有一些線程執行任務較快,在做完自己的工作之後,這些線程將嘗試發現那些未被執行的任務,
如果找到,則執行這些任務。
Fork/Join框架採用雙端隊列(Deque)作爲任務的存儲結構,該隊列支持後進先出的數據pop和push操作,並且支持先進先出的take操作。由某一工作線程創建的子任務仍然會被加入到線程的隊列中,一般採用push操作,工作線程從自己的
雙端隊列中取出執行任務,一般採用pop操作,當工作線程需要從其他線程的工作隊列中竊取任務時,一般採用take操作。
線程池中的線程每次是從隊列的頭部取出任務來執行,當使用使用fork操作產生新任務時,會把新的任務加入到隊列的頭部,而不像其他線程池加入到尾部,這樣可以保證fork出來的新的任務可以儘快得到執行。
當某個線程執行完自己的任務,而沒有其他任務可處理時,就從隊列尾部竊取一個任務執行。
Fork/Join 框架的編程技術
java中Fok/Join框架比較適於解決那些具有遞歸性質,可以進行任務分解的程序。
Fork/Join框架需要對任務進行分解和合並操作,在分解前,首先查看問題的規模是否超過了預設門檻值(Threshold Value),在任務規模不大的情況下,採用串行的解決方式,由於該方式省去了分解和合並的操作有時效果會更好。
在使用Fork/Join框架時,門檻值通常時人爲地進行設定,當問題地規模小於門檻值時,說明沒必要採用並行地解決方式,因此更傾向於採用串行執行方式或者採用其他更優化地算法解決;
而當問題地規模大於門檻值時,採用Fork/Join框架求解。
使用Fork/Join框架編程模式:如下所示:
if(問題模式<門檻值){
//使用串行模式解決或者其他模式解決
}else{
//將任務Task盡心分解,分解爲若干個小任務 Task1,Task2.......
//將任務Task1、Task2提交到線程池執行
//如果任務有返滬結果,收集結果
}
ForkJoinPool 類
ForkJoinPool類是Fork/Join框架地核心,也是Fork/Join框架執行地入口點,它是實現了接口Executor Service。ForkJoinPool類地任務是負責管理線程,並提供線程執行狀態和任務處理地相關信息。
ForkJoinPool類區別於其他ExecutorService的地方在於該類實現了工作竊取算法,在線程池中的線程總是嘗試發現其他可以運行的任務,可以通過相關的方法獲取工作竊取的執行情況。
ForkJoinPool的創建
ForkJoinPool類從AbstractExecutorService類繼承,主要用於處理ForkJoinTask中的任務。
以下是ForkJoinPool類構造方法的形式:
1)ForkJoinPool(); //該構造方法將默認生成一個線程池,線程池中可以同時運行的線程數和CPU能夠同時運行的最大線程數(可以通過Runtime.getRuntim.availableProcessors()獲取)相同。
2)ForkJoinPool(int parallelism);//用戶可以指定線程池中線程的數目。
//parallelism 指明並行運行的線程數
//factory 是線程工廠,用於創建新線程
//handler 用戶處理內部出現的異常
//asyncMode 用於指定ForkJoinPool的工作模式,當爲true時,表示工作本地的先進先出模式。 默認爲false。對於某些應用來講,爲true比默認的更加合適。
3)ForJoinPool(int parallelism,ForkJoinWorkThreadFactory factory,Thread.UncaughtExceptionHandler handler,boolean asyncMode);//
demo 示例:
創建一個ForkJoinPool
ForkJoinPool pool = new ForkJoinPool();
ForkJoinPool pool = new ForkJoinPool(8);
ForkJoinPool的使用:
ForkJoinPool的使用大致可以分爲兩種,一種時通過invoke、execute和submit執行的任務,另一種時在程序執行過程中通過fork操作來執行的任務。
ForkJoinPool常用的方法:
1)invoke(ForkJoinTask<T> task);//是一個同步調用方法,用於處理給定任務,並在處理完畢後返回處理的結果,返回結果的類型由T指定。
2)invokeAll(Collection<? extends Callable<T> >tasks);//是一個同步調用方法,可以將若干個任務組成的一個集合交給ForkJoinPool執行,任務在繼續運行之前會等待子任務的結束,該方法
返回任務的結果,結果類型由T指定。ForkJoinTask類的該方法是Executor和Fork/Join框架的主要不同之處。
3)execute(Runnable task);//可以把一個Runnable線程所有代表的任務收納櫃ForkJoinPool中,需要指出的是:ForkJoinPool不會對Runnable對象使用工作竊取算法,該算法只會被應用到ForkJoinTask對象中。
同樣,ForkJoinPool不會對Callable對象使用工作竊取算法。
4)submit(ForkJoinTask<T> task);//用於把一個任務提交給ForkJoinPool執行,返回ForkJoinTask<T>的結果。
以上三類執行任務的方法,具體使用如下:
------------------------------------------------------------------------------------------------------------------------
| 在外部對Fork/Join操作的調用 | 在Fork/Join框架範圍內使用
------------------------------------------------------------------------------------------------------------------------
異步執行 | execute(ForkJoinTask) | ForkJoinTask.fork()
------------------------------------------------------------------------------------------------------------------------
同步執行(等待子任務完成) | invoke(ForkJoinTask) | ForkJoinTask.invoke()
------------------------------------------------------------------------------------------------------------------------
執行並獲得結果 | submit(ForkJoinTask) | ForkJoinTask.fork()
------------------------------------------------------------------------------------------------------------------------
Fork/Join框架中的任務
ForkJoinTask類是所有的在Fork/Join框架中執行任務的基類,提供了一系列機制來實現Fork和Join操作,該類由兩個子類,分別是RecursiveAction和RecursiveTask,從RecursiveAction類繼承類的子類方法一般沒有返回值,
從RecursiveTask類繼承的子類則由返回值。
public abstract class ForkJoinTask<?> extends Object implements Future<V>,Serializable
ForkJoinTask 類實現了接口Serializable 和 Future<V>,所以一般在子類加入serialVersionUID變量定義。 如:
private static final long SerialVersionUID = 1L;
Fork/Join 任務的創建
在創建任務時,最好不要從ForkJoinTask類直接繼承,而是從該類的子類RecursiveAction或RecursiveTask繼承。
1)從RecursiveAction 繼承承建任務
從RecursiveAction類繼承的子類方法一般沒有返回值。繼承後的新類需要重寫該類的computer()方法。
computer()方法的形式如下:
@Override
public void computer(){
//方法體
}
isDone()方法,用於判斷任務是否完成。
cancel(booleanmayInterrupteIfRunning)用於取消一個任務的執行。
當任務提交到ForkJoinPool後獲得運行的機會。
demo 示例:
使用Fork/Join 框架對班級的人數進行更新。當需要更新班級的人超過10時,更新任務進行細分。
分析:Fork/Join 框架的門檻 10
//班級的對象類
public class ClassInfo{
private String name;
private int number;
public ClassInfo( String name,int number){
this.name = name;
this.number = number;
}
public String getName(){
return name;
}
public int getNumber(){
return number;
}
public void setNumber(int number){
this.number = number;
}
}
/更新操作的任務類
public class UpdateTask extends RecursiveAction{
private static final long serialVersionUID =1L;
private List<ClassInfo> classInfos;
private int start;
private int end;
private int increment;
private int nthreads;
private int threshold;
public UpdateTask(List<ClassInfo> classInfos,int start,int end,int increment,int nthreads,int threshold){
this.classInfos = classInfos;
this.start = start;
this.end = end;
this.increment = increment;
this.nthreads = nthreads;
this.threshold = threshold;
}
@Override
public void compute(){
//門檻 串行
if(end-start<=threshold | threshold ==1){
updateSequential();
}else{
UpdateTask [] tasks = new UpdateTask[nthreads];
int [] data = new int[nthreads+1];
int segment = (end -start+nthreads-1)/nthreads;
for(int i=0;i<=nthreads;i++){
data[i] = start+segment*i;
if(data[i]>end){
data[i] = end;
}
}
int mid = (end=start)/2;
for(int i=0;i<nthreads;i++){
tasks[i] = new UpdateTask(classInfos,data[i],data[i+1],increment,nthreads,threshold);
}
invokeAll(tasks);
}
}
public void updateSequential(){
for(int i = start;i<end;i++){
ClassInfo classInfo = classInfos.get(i);
classInfo.setNumber(classInfo.getNumber()+increment);
}
}
}
//啓動測試類
public class Index{
public static void main(String [] args ){
int nthreads = Runtime.getRuntime().availableProcessors();
int threshold = 10;
int increment = 5;
int baseNum = 50;
int size = 10000;
List<ClassInfo> classInfos = new ArrayList<ClassInfo>();
for(int i=0;i<size;i++){
ClassInfo classInfo = new ClassInfo("班級"+i,baseNum);
classInfos.add(classInfo);
}
ForkJoinPool pool = new ForkJoinPool();
UpdateTask updateTask = new UpdateTask(classInfos,0,classInfos.size(),increment,nthreads,threshold);
pool.execute(updateTask);
do{
System.out.printf("類Index:並行度:%d\n",pool.getParallelism());
System.out.printf("類Index:活動線程數:%d\n",pool.getActiveThreadCount());
System.out.printf("類Index:任務數:%d\n",pool.getQueuedTaskCount());
System.out.printf("類Index:%d\n",pool.getStealCount());
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
}while(!updateTask.isDone());
if(validate(classInfos,baseNum+increment)){
System.out.println("所有班級更新完畢!");
}else{
System.out.println("Something wrong happend.");
}
}
public static boolean validate(List<ClassInfo> classInfos, int total){
boolean pass = true;
for(ClassInfo info:classInfos){
if(info.getNumber()!=total){
pass = false;
}
}
return pass;
}
}
運行結果:
類Index:並行度:8
類Index:活動線程數:0
類Index:任務數:0
類Index:41
所有班級更新完畢!