上篇部落格我們介紹了通過CyclicBarrier使線程同步,但是上述方法存在一個問題,那就是如果一個大任務跑了2個線程去完成,如果線程2耗時比線程1多2倍,線程1完成後必須等待線程2完成,等待的過程線程1沒法複用。現在我們準備解決這個問題,我們希望線程1完成自己的任務後能去協助線程2完成一部分任務。Java7引如了Fork/Join架構可以很好的解決這個問題。
Fork/Join是一個用於並存執行任務的架構,是一個把大任務分割成若干個小任務,最後匯總每個小任務結果後得到大任務結果的架構。fork是分叉,join是結合。下面看張圖來清晰的認識一下:
其實Fork/Join本質上是分治演算法的一種實現。下面我們來看怎麼具體使用:
ForkJoinTask:我們要使用Fork/Join架構,必須首先建立一個Fork/Join任務。它提供在任務中執行fork()和join()操作的機制,通常情況下我們不需要直接繼承ForkJoinTask類,而只需要繼承它的子類,Fork/Join架構提供了以下兩個子類:
RecursiveAction:用於沒有返回結果的任務。
RecursiveTask :用於有返回結果的任務。
ForkJoinPool:ForkJoinTask需要通過ForkJoinPool來執行,任務分割出的子任務會添加到當前背景工作執行緒所維護的雙端隊列中,進入隊列的頭部。當一個背景工作執行緒的隊列裡暫時沒有任務時,它會隨機從其他背景工作執行緒的隊列的尾部擷取一個任務。
下面,我們同樣實現考試系統抽題的例子。 GetQuestionsTask.java
public class GetQuestionsTask extends RecursiveTask<List> {//參數mapprivate Map map;//參數list==只放題型private List questionTypeList;public GetQuestionsTask(List questionTypeList,Map map) {this.questionTypeList = questionTypeList;this.map=map;}@Overrideprotected List compute() {System.out.println(questionTypeList.size());List list = new ArrayList();if (questionTypeList.size() < 2) {// 抽題list = getQuestions(questionTypeList,map);} else {int size = questionTypeList.size();int mid = size / 2;GetQuestionsTask task1 = new GetQuestionsTask(questionTypeList.subList(0, mid),map);GetQuestionsTask task2 = new GetQuestionsTask(questionTypeList.subList(mid, size),map);invokeAll(task1, task2);try {list = groupResults(task1.get(), task2.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}return list;}//合并抽題結果private List groupResults(List list1, List list2) {System.out.println(Thread.currentThread().getName()+"開始合并結果......");// 合并返回結果List list = new ArrayList();list.addAll(list1);list.addAll(list2);System.out.println(Thread.currentThread().getName()+"合并結果結束......");return list;}// 抽題private List getQuestions(List questTypeList,Map map) {List list = new ArrayList();for(int i=0;i<questTypeList.size();i++){System.out.println(Thread.currentThread().getName()+"開始抽題......"+questionTypeList.get(i).toString());//假資料,向list中放試題list.add("0");list.add("1");list.add("2");list.add("3");list.add("4");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"抽題結束..."+questionTypeList.get(i).toString());}return list;}}
用戶端:
//該池的線程數量不會超過0*7fff個(32767)//池中維護著ForkJoinWorkerThread對象數組,數組大小由parallelism屬性決定,parallelism預設為處理器個數ForkJoinPool pool = new ForkJoinPool();GetQuestionsTask task = new GetQuestionsTask(questionTypeList, map);pool.execute(task);// 試題列表=task.get()try {List finalList = task.get();System.out.println("最終結果個數:" + finalList.size());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}
總結:
Fork/Join實現了“工作竊取演算法”,當一個背景工作執行緒的隊列裡暫時沒有任務時,它會隨機從其他背景工作執行緒的隊列的尾部擷取一個任務。當然,fork/join架構的使用有一定的約束條件:
1.除了fork() 和 join()方法外,線程不得使用其他的同步工具。線程最好也不要sleep()
2.線程不得進行I/O操作
3.線程不得拋出checked exception。