Java ForkJoin 框架初探

多核時代,編程語言如果不支持多核編程就OUT了,Java爲了迎頭趕上,在Java 8 版本增加大量支持多核編程的類庫,如Stream等,Java 7開始支持的ForkJoin框架也是爲了更好的支持多核編程。


設計思想:化整爲零再化零爲整,另外還要加上一種團隊精神,即能者多勞。化整爲零(split up)就是把一個複雜的任務分爲許多足夠小的任務計算;化零爲整(merge)就是把小任務的計算結果不斷往上合併值到得出最終結果;團隊精神:ForkJoin使用了Work-Stealing算法,即先完成任務的線程不會閒着,會主動去偷別的線程待處理任務隊列中的任務來幫忙處理,直到全部任務都處理完大夥才能停下來休息。


使用ForkJoin框架經常使用到兩個類RecursiveTask 和 RecursiveAction,RecursiveTask 用於定義有返回值的任務,RecursiveAction用於定義沒有返回值的任務,從類名看這兩個類應該跟遞歸有一腿?經確認,ForkJoin框架處理的任務基本都能使用遞歸處理,比如求斐波那契數列等,但遞歸算法的缺陷是:一隻會只用單線程處理,二是遞歸次數過多時會導致堆棧溢出;ForkJoin解決了這兩個問題,使用多線程併發處理,充分利用計算資源來提高效率,同時避免堆棧溢出發生。當然像求斐波那契數列這種小問題直接使用線性算法搞定可能更簡單,實際應用中完全沒必要使用ForkJoin框架,所以ForkJoin是核彈,是用來對付大傢伙的,比如超大數組排序。


最佳應用場景:多核、多內存、可以分割計算再合併的計算密集型任務。


ForkJoinTask類的幾個重要方法:


fork()方法:將任務放入隊列並安排異步執行,一個任務應該只調用一次fork()函數,除非已經執行完畢並重新初始化。


tryUnfork()方法:嘗試把任務從隊列中拿出單獨處理,但不一定成功。


join()方法:等待計算完成並返回計算結果。


isCompletedAbnormally()方法:用於判斷任務計算是否發生異常。


ForkJoinPool的使用與其它ExecutorService類似。


示例代碼:

package com.stevex.app.forkjoin;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

public class ForkJoinTest {
	public static void main(String[] args) {
		long beginTime = System.nanoTime();		
		System.out.println("The sum from 1 to 1000 is " + sum(1, 1000));
		System.out.println("Time consumed(nano second) By recursive algorithm : " + (System.nanoTime() - beginTime));
		
		
		beginTime = System.nanoTime();	
		System.out.println("The sum from 1 to 1000000000 is " + sum1(1, 1000000000));	
		System.out.println("Time consumed(nano second) By loop algorithm : " + (System.nanoTime() - beginTime));
		
		
		ForkJoinTest app = new ForkJoinTest();
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		CountTask task = app.new CountTask(1,1000000000);
		beginTime = System.nanoTime();
		Future<Long> result = forkJoinPool.submit(task);
		try{
			System.out.println("The sum from 1 to 1000000000 is " + result.get());			
		}
		catch(Exception e){
			e.printStackTrace();
		}
		
		System.out.println("Time consumed(nano second) By ForkJoin algorithm : " + (System.nanoTime() - beginTime));
	}

	private static long sum1(long start, long end) {
		long s = 0l;
		
		for(long i=start; i<= end; i++){
			s += i;
		}
		
		return s;
	}

	private static long sum(long start, long end){
		if(end > start){
			return end + sum(start, end-1);
		}
		else{
			return start;
		}
	}
	
	private class CountTask extends RecursiveTask<Long>{
		private static final int THRESHOLD = 10000;
		private int start;
		private int end;
		
		public CountTask(int start, int end){
			this.start = start;
			this.end = end;
		}
		
		protected Long compute(){
			//System.out.println("Thread ID: " + Thread.currentThread().getId());
			
			Long sum = 0l;
			
			if((end -start) <= THRESHOLD){
				sum = sum1(start, end);
			}
			else{
				int middle = (start + end) / 2;
				CountTask leftTask = new CountTask(start, middle);
				CountTask rightTask = new CountTask(middle + 1, end);
				leftTask.fork();
				rightTask.fork();
				
				Long leftResult = leftTask.join();
				Long rightResult = rightTask.join();
				
				sum = leftResult + rightResult;
			}
			
			return sum;
		}
	}
}


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