ForkJoin框架
ForkJoin是Java7提供的一個並行執行任務的框架,是把大任務分割成若干個小任務,待小任務完成後將結果彙總成大任務結果的框架。
工作竊取演算法
工作竊取演算法是指某個執行緒從其他佇列裡竊取任務來執行。
大任務分割為若干互不依賴的子任務,為了減少執行緒間的競爭,把這些子任務分別放到不同的佇列裡,併為每個佇列建立一個單獨的執行緒來執行佇列裡的任務,執行緒和佇列一一對應。
比如A執行緒負責處理A佇列裡的任務。但是,有的執行緒會先把自己佇列裡的任務幹完,而其他執行緒對應的佇列裡還有任務等待處理。幹完活的執行緒去幫其他執行緒幹活,於是它就去其他執行緒的佇列裡竊取一個任務來執行。
在竊取過程中兩個執行緒會訪問同一個佇列,為了減少竊取任務執行緒和被竊取任務執行緒之間的競爭,通常會使用雙端佇列(LinkedBlockingDeque)。被竊取任務的執行緒永遠從佇列的頭部拿取任務,竊取任務的執行緒從佇列尾部拿取任務。
- 優點:充分利用執行緒進行平行計算,減少了執行緒間的競爭。
- 缺點:在某些情況下還是存在競爭,比如雙端佇列裡只有一個任務時。並且該演算法會消耗了更多的系統資源,比如建立多個執行緒和多個雙端佇列。
侷限性:
1、任務只能使用fork和join作為同步機制,如果使用了其他同步機制,當他們在同步操作時,工作執行緒就不能執行其他任務了。比如在fork框架使任務進入了睡眠,那麼在睡眠期間內在執行這個任務的執行緒將不會執行其他任務了。
2、我們所拆分的任務不應該去執行IO操作,如讀和寫資料檔案。
3、任務不能丟擲檢查異常。必須通過必要的程式碼來處理他們。
Fork/Join框架的設計
步驟1 分割任務。 fork類來把大任務分割成子任務(若子任務還是很大,就不停地分割,直到分割出的子任務足夠小。)
步驟2 執行任務併合並結果。 分割的子任務分別放在雙端佇列裡,然後幾個啟動執行緒分別從雙端佇列裡獲取任務執行。
子任務執行完的結果都統一放在一個佇列裡,啟動一個執行緒從佇列裡拿資料,然後合併這些資料。
框架核心: ForkJoinTask |ForkJoinPool
使用ForkJoin框架,首先必須建立一個ForkJoin任務 。
ForkJoinTask:提供在任務中執行fork和join的機制。
通常情況下不需要直接繼承ForkJoinTask類,只需要繼承它的子類:
- RecursiveAction:用於沒有返回結果的任務。
- RecursiveTask:用於有返回結果的任務。
ForkJoinPool:負責來做實現,包括工作竊取演算法、管理工作執行緒和提供關於任務的狀態以及他們的執行資訊。
Fork/Join框架的使用
//Recursive(遞迴的;迴圈的)
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;// 閾值
private int start;
private int end;
public ForkJoinTaskExample(int start, int end) {
this.start = start;
this.end = end;
}
//ForkJoinTask與一般任務的主要區別在於它需要實現compute方法
@Override
protected Integer compute() {
int sum = 0;
//如果任務足夠小就計算任務
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (int i = start; i <= end; i++) {
sum += i;
}
} else {
// 如果任務大於閾值,就分裂成兩個子任務計算(分裂演算法,可依情況調優)
int middle = (start + end) / 2;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
//每個子任務在呼叫fork方法時,又會進入compute方法看看當前子任務是否需要繼續分割成子任務
//如果不需要繼續分割,則執行當前子任務並返回結果。
leftTask.fork();
rightTask.fork();
// 等待任務執行結束 並得到其結果
int leftResult = leftTask.join();
int rightResult = rightTask.join();
// 合併子任務
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkjoinPool = new ForkJoinPool();
//生成一個計算任務,計算1+2+3+4...100
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
//執行一個任務
Future<Integer> result = forkjoinPool.submit(task);
try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}
異常處理
ForkJoinTask在執行的時候可能會丟擲異常,但是我們沒辦法在主執行緒裡直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經丟擲異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。
//getException方法返回Throwable物件,
//如果任務被取消了則返回CancellationException。
//如果任務沒有完成或者沒有丟擲異常則返回null。
if(task.isCompletedAbnormally()){
System.out.println(task.getException());
}
Fork/Join框架的實現原理
ForkJoinPool由ForkJoinTask陣列和ForkJoinWorkerThread陣列組成,ForkJoinTask陣列負責將存放的任務提交給ForkJoinPool,而ForkJoinWorkerThread陣列負責執行這些任務。
(1)ForkJoinTask的fork方法實現原理
當我們呼叫ForkJoinTask的fork方法時,程式會呼叫ForkJoinWorkerThread的pushTask方法非同步地執行這個任務,然後立即返回結果。
public final ForkJoinTask<V> fork() {
((ForkJoinWorkerThread) Thread.currentThread()).pushTask(this);
return this;
}
pushTask方法把當前任務存放在ForkJoinTask陣列佇列裡。然後再呼叫ForkJoinPool的signalWork()方法喚醒或建立一個工作執行緒來執行任務。
final void pushTask(ForkJoinTask<> t) {
ForkJoinTask<>[] q; int s, m;
if ((q = queue) != null) { // ignore if queue removed
long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
UNSAFE.putOrderedObject(q, u, t);
queueTop = s + 1; // or use putOrderedInt
if ((s -= queueBase) <= 2)
pool.signalWork();
else if (s == m)
growQueue();
}
}
(2)ForkJoinTask的join方法實現原理
Join方法的主要作用是阻塞當前執行緒並等待獲取結果。
public final V join() {
//首先通過doJoin()方法得到當前任務的狀態
if (doJoin() != NORMAL)
return reportResult();
else
return getRawResult();
}
private V reportResult() {
int s; Throwable ex;
if ((s = status) == CANCELLED)
throw new CancellationException();
if (s == EXCEPTIONAL && (ex = getThrowableException()) != null)
UNSAFE.throwException(ex);
return getRawResult();
}
任務狀態有4種:
- 訊號(SIGNAL)
- 已完成(NORMAL),則直接返回任務結果。
- 被取消(CANCELLED),則直接丟擲CancellationException。
- 丟擲異常(EXCEPTIONAL),則直接丟擲對應的異常。
private int doJoin() {
Thread t; ForkJoinWorkerThread w; int s; boolean completed;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
//首先通過檢視任務的狀態
if ((s = status) < 0)
return s;//如果執行完成,則直接返回任務狀態
//任務沒有執行完,則從任務陣列裡取出任務並執行
if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
try {
completed = exec();
} catch (Throwable rex) {
//記錄異常,並將任務狀態設定為EXCEPTIONAL。
return setExceptionalCompletion(rex);
}
if (completed)
//任務順利執行完成,則設定任務狀態為NORMAL
return setCompletion(NORMAL);
}
return w.joinTask(this);
}
else
return externalAwaitDone();
}
相關文章
- java8之ForkJoin框架的使用Java框架
- ForkJoin框架的RecursiveTask和ForkJoinPool的使用案例框架
- 併發程式設計:DEMO:比較Stream和forkjoin框架的效率程式設計框架
- Golang實現ForkJoin小文Golang
- 六問六答理解ForkJoin原理
- 併發程式設計之:ForkJoin程式設計
- Java併發程式設計ForkJoin的DemoJava程式設計
- 多執行緒系列(二十一) -ForkJoin使用詳解執行緒
- ForkJoin和氣泡排序組合實現的歸併排序排序
- Android精通之OrmLite資料庫框架,Picasso框架,Okio框架,OKHttp框架AndroidORM資料庫框架HTTP
- bo 框架之 beego 框架 model curd框架Go
- 微服務框架-dubbo整合nacos框架微服務框架
- 最好的 Go 框架:沒有框架?Go框架
- golang web框架,golang版本laravel 框架GolangWeb框架Laravel
- 前端框架開發之Niu框架——從零學框架的小白前端框架
- Django框架,Flask框架和Tornado框架各有什麼優缺點Django框架Flask
- 執行時框架,編譯時框架框架編譯
- 【SSH框架】系列之 Spring 整合 Hibernate 框架框架Spring
- Spring框架 - Spring和Spring框架組成Spring框架
- HTML 框架HTML框架
- LayUI框架UI框架
- MVC框架MVC框架
- picasso框架框架
- Gorm框架GoORM框架
- express框架Express框架
- Hibernate框架框架
- SSM框架SSM框架
- 全新框架框架
- 前端框架前端框架
- Truffle框架框架
- cms 框架框架
- Mybatis框架MyBatis框架
- Scrapy框架框架
- 集合框架框架
- 基本框架框架
- 【JavaScript框架封裝】公共框架的封裝JavaScript框架封裝
- Scrapy框架的使用之Scrapy框架介紹框架
- java框架之Hibernate框架知識點整理。Java框架