FutureTask
首先講一下FutureTask,它表示的是一種,異步操作的典範。我提交了任務,在未來我要拿到結果。
考慮一種簡單的場景,甲問乙一個問題,乙一時回答不了,乙要去考慮一段時間(查一下資料),等到有結果了,再告訴甲。
這時,我們需要類甲,類乙。
它的使用原理可以博主之前的篇文章:https://blog.csdn.net/qq_41864967/article/details/100737036(需要實現Callable接口或者Runable)
Future類就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。
必要時,通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。
FutureTask實現了RunnableFuture,所以它既可以作爲Runnable被線程執行,又可以作爲Future得到Callback的返回值。
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
分析:FutureTask除了實現了Future接口外還實現了Runnable接口(即可以通過Runnable接口實現線程,也可以通過Future取得線程執行完後的結果),因此FutureTask也可以直接提交給Executor執行。
Fork/Join
Fork/Join 框架是Java7提供的一個用於並行執行任務的框架。
是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。
工作竊取算法
工作竊取(work-stealing)算法是指某個線程從其他隊列裏竊取任務來執行。
假如需要做一個比較大的任務,我們可以把這個任務分割爲若干互不依賴的子任務,爲了減少線程間的競爭,於是把這些子任務分別放到不同的隊列裏,併爲每個隊列創建一個單獨的線程來執行隊列裏的任務,線程和隊列一一對應,比如A線程負責處理A隊列裏的任務。但是有的線程會先把自己隊列裏的任務幹完,而其他線程對應的隊列裏還有任務等待處理。幹完活的線程與其等着,不如去幫其他線程幹活,於是它就去其他線程的隊列裏竊取一個任務來執行。而在這時它們會訪問同一個隊列,所以爲了減少竊取任務線程和被竊取任務線程之間的競爭,通常會使用雙端隊列,被竊取任務線程永遠從雙端隊列的頭部拿任務執行,而竊取任務的線程永遠從雙端隊列的尾部拿任務執行。
fork/join框架的核心是ForkJoinPool
類,該類繼承了AbstractExecutorService類。ForkJoinPool
實現了工作竊取算法並且能夠執行 ForkJoinTask
任務。
公共抽象類ForkJoinTask
這個類的就是fork/join的基石類了。實現了Future接口。可以看成是一個輕量級的Future。
兩個重要方法:
//安排在當前任務運行的池中異步執行此任務,有自定義線程池的話,就在自定義線程池裏面,沒有,就在公共線程池裏面。
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
當isDone()返回true時候,返回結果。
/**
* Returns the result of the computation when it {@link #isDone is
* done}. This method differs from {@link #get()} in that
* abnormal completion results in {@code RuntimeException} or
* {@code Error}, not {@code ExecutionException}, and that
* interrupts of the calling thread do <em>not</em> cause the
* method to abruptly return by throwing {@code
* InterruptedException}.
*
* @return the computed result
*/
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
join()和future接口的get()方法作用很相似。區別是 join()方法不會拋出異常,get()會拋出異常。這兩個都會阻塞等待結果。
公共類ForkJoinPool
fork/join專用線程池,和其他線程池最大的區別是,這個線程池裏面的線程,會嘗試查找 任務執行。其他線程池則需要自己提交。實現了work-stealing(工作竊取)算法。
構造方法:
可以由 boolean asyncMode 決定是否異步。
一般的,我們調用ForkJoinPool .commonPool()返回一個實例。這是系統幫我們定義好的,可以使用公共的線程池。
提交任務的幾種方法:
void execute(ForkJoinTask<?> task) 異步提交執行,調用其fork方法在多個線程之間拆分工作,然後通過task的get(),或者join()得到結果。
<T> ForkJoinTask<T> submit(ForkJoinTask<T> task) 提交ForkJoinTask以供執行,完成時返回一個future對象用於檢查狀態以及運行結果。然後通過返回的ForkJoinTask的get(),或者join()得到結果。
<T> T invoke(ForkJoinTask<T> task) 執行給定任務,完成後返回結果,會等待獲得結果。
List < Future > invokeAll(Collection <?extends Callable > tasks)執行給定的任務,返回完成所有狀態和結果的Futures列表。
fork/join 就是特殊的線程池(ForkJoinPool)和特殊的future(ForkJoinTask)的配合。
官方寫了兩個具體的ForkJoinTask子類,我們平時使用時候,只需要extend 子類就行了。
ForkJoinTask子類
- RecursiveAction 用於大多數不返回結果的計算
- RecursiveTask 會返回最終結果
我們繼承上面的類的時候,重寫compute()方法就行。
使用例子如下:
RecursiveTask 例子:
public class Fibonacci extends RecursiveTask<Integer> {
final int[] n;
final int start;
final int end;
/**
* 任務分段 判斷值
*/
final int part = 1000_0;
/**
* @param n 數組
* @param start 計算區間 開始
* @param end 計算區間 結束
*/
public Fibonacci(int[] n, int start, int end) {
this.n = n;
this.start = start;
this.end = end;
if (start > end || start < 0 || end > n.length) {
throw new IllegalArgumentException();
}
}
@Override
protected Integer compute() {
//小於計算空間 直接求和
if ((end - start) <= part) {
//可變參數本質就是先創建了一個數組,該數組的大小就是可變參數的個數
return sum(start, end, n);
}
//進行任務切割 劃分
Fibonacci f1 = new Fibonacci(n, start, start + part);
//安排在當前任務運行的池中異步執行此任務(如果適用)
f1.fork();//異步執行 也就是用另一個線程進行運算
Fibonacci f2 = new Fibonacci(n, start + part, end);
//join 返回計算結果,這個類似於get, 這個不會拋出異常
return f2.compute() + f1.join();
}
private int sum(int start, int end, int... ints) {
int temp = 0;
for (int i = start; i < end; i++) {
temp += ints[i];
}
return temp;
}
}
test類:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
int[] longs = new int[1000_000_0];
for (int i = 0; i < 1000_000_0; i++) {
longs[i] = i;
}
Fibonacci fibonacci = new Fibonacci(longs, 0, longs.length);
long before = System.currentTimeMillis();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>" + fibonacci.isDone());
}
};
Timer timer = new Timer();
timer.schedule(timerTask, 0, 1000);
//可以自定義 forkjoin線程池
ForkJoinPool forkJoinPool = new ForkJoinPool(100);
//使用公共線程池提交任務
int result = ForkJoinPool.commonPool().invoke(fibonacci);
long time = System.currentTimeMillis() - before;
long before1 = System.currentTimeMillis();
int result1 = sum(longs);
long time1 = System.currentTimeMillis() - before1;
System.out.println(result + " " + time + " " + result1 + " " + time1);
}
public static int sum(int[] ints) {
int temp = 0;
for (int i = 0; i < ints.length; i++) {
temp += ints[i];
}
return temp;
}
}
RecursiveAction 例子:
public class PrintTask extends RecursiveAction {
private final int Max = 50;
private int start;
private int end;
public PrintTask(int start,int end){
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if((end - start)<Max){
for(int i=start;i<end;i++){
System.out.println("當前線程:"+Thread.currentThread().getName()+" i :"+i);
}
}else{
int middle = (start+end)/2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
left.fork();
right.fork();
}
}
}
test類:
public class Test {
public static void main(String[] args) throws InterruptedException {
ForkJoinPool forkJoin = new ForkJoinPool();
forkJoin.submit(new PrintTask(0, 200));
forkJoin.awaitTermination(2, TimeUnit.SECONDS);
forkJoin.shutdown();
}
}
結果: