並行執行任務的Fork/Join框架
背書中(引用書上的話):Java7中提供了用於並行執行任務的Fork/Join框架, 可以把任務分成若干個分任務,最終彙總每個分任務的結果得到總任務的結果。這篇我們來看看Fork/Join框架。
先舉個例子
一個字串陣列,需要把每個元素中的*字元的索引返回,並求和(自己編了個栗子,沒有撒實際意義),用Fork/Join框架來實現,可以定義一個處理字串陣列的總任務,然後把總任務拆分,把陣列中每個字串交給子任務去處理,然後等待子任務執行完畢,彙總結果,並返回:
package thread.ForkJoin;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* @Description: .
* @Author: ZhaoWeiNan .
* @CreatedTime: 2017/6/21 .
* @Version: 1.0 .
*/
public class StringTask extends RecursiveTask<Integer>{
//要處理的字串
private String dest;
public StringTask(String dest) {
this.dest = dest;
}
//父類RecursiveTask是一個抽象類,所以需要實現compute方法
@Override
protected Integer compute() {
if (dest == null || "".equals(dest))
return 0;
//判斷字串中 * 的索引,並返回
return dest.indexOf("*");
}
}
class ArrayTask extends RecursiveTask<Integer>{
//需要處理的字串陣列
private String[] array;
public ArrayTask(String[] array) {
this.array = array;
}
@Override
protected Integer compute() {
if (array == null || array.length < 1)
return 0;
//申明一個StringTask變數,作為子任務
StringTask stringTask;
//定義一個子任務佇列,用於任務執行完畢後,獲取子任務的執行結果
List<StringTask> list = new ArrayList<>();
int sum = 0;
//把字串陣列的中每一個字串分給多個StringTask子任務去處理
for (String s : array){
//建立一個變數,作為子任務去處理字串
stringTask = new StringTask(s);
//執行子任務
stringTask.fork();
//加入子任務佇列
list.add(stringTask);
}
for (StringTask task : list){
//等子任務執行完畢,獲取子任務執行的結果,並累加
sum += task.join();
}
return sum;
}
}
class Demo{
public static void main(String[] args){
//初始化字串陣列
String[] array = new String[]{"#####*####","##*########","###*#######","#*############"};
//建立一個總任務,處理字串陣列
ArrayTask arrayTask = new ArrayTask(array);
//建立執行任務的執行緒池ForkJoinPool物件
ForkJoinPool forkJoinPool = new ForkJoinPool();
//執行總任務
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(arrayTask);
//返回任務的結果
try {
System.out.println(forkJoinTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
程式碼放到了開源中國:http://git.oschina.net/zhaoweinan/forkjoin,有興趣的小夥伴可以拿去
總的來說,Fork/Join框架就是一個用來並行執行任務的框架,可以把一個大任務,分成若干個子任務,等各個子任務執行完畢,可以把他們的執行結果獲取到,並匯聚,起到了並行執行任務作用。
Fork/Join框架的構成
1.ForkJoinPool
ForkJoinPool繼承了AbstractExecutorService抽象類,AbstractExecutorService實現了ExecutorService介面,由此看來ForkJoinPool也是執行緒池家族的一員,
ForkJoinPool使用invoke、execute、submit用來執行任務。
2.ForkJoinTask
ForkJoinTask是Fork/Join框架使用的任務類,實現了Future介面,我們一般使用它的兩個子類RecursiveTask和RecursiveAction,
public abstract class RecursiveAction extends ForkJoinTask<Void> {
private static final long serialVersionUID = 5232453952276485070L;
RecursiveAction適用於沒有返回結果的任務,
public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
private static final long serialVersionUID = 5232453952276485270L;
RecursiveTask適用於有返回值的任務。
Work-Stealing (工作竊取)
粗略說一下Work-Stealing,ForkJoinPool具有 Work-Stealing (工作竊取)的能力,什麼意思呢?就拿文章開頭的栗子來說,把處理字串陣列的大任務,分成了若干個處理字串的子任務,這些子任務執行緒執行完畢後,不會閒著,回去執行別的子任務,通俗的來說,Work-Stealing (工作竊取)就是執行緒從其他佇列裡面獲取任務來執行。
Work-Stealing的優點
充分利用了執行緒,提高了執行緒並行執行任務的效率,並減少了執行緒間競爭帶來的系統開銷。
Work-Stealing的缺點
存在競爭的情況,而且佔用了更多的系統資源。
Fork/Join框架原理
ForkJoinPool分析
貼一張ForkJoinPool的類圖
注意箭頭所指的兩個屬性,
ForkJoinTask<?>陣列submissionQueue,存放程式加到ForkJoinPool的任務
private ForkJoinTask<?>[] submissionQueue;
ForkJoinWorkerThread類繼承了Thread,是一個執行緒類, ForkJoinWorkerThread[] workers就是一個執行緒陣列,負責去執行submissionQueue中的任務
ForkJoinWorkerThread[] workers;
.....
public class ForkJoinWorkerThread extends Thread
ForkJoinTask分析
fork方法
獲取當前ForkJoinWorkerThread執行緒,呼叫ForkJoinWorkerThread的pushTask方法執行ForkJoinTask任務
public final ForkJoinTask<V> fork() {
//獲取當前ForkJoinWorkerThread執行緒,呼叫ForkJoinWorkerThread的pushTask方法執行任務
((ForkJoinWorkerThread) Thread.currentThread())
.pushTask(this);
return this;
}
再來看看ForkJoinWorkerThread的pushTask方法:
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)
//呼叫執行緒池ForkJoinPool的signalWork方法
pool.signalWork();
else if (s == m)
growQueue();
}
}
ForkJoinWorkerThread的pushTask方法把任務ForkJoinTask加到了ForkJoinTask[]任務陣列中,並呼叫了ForkJoinPool執行緒池的signalWork方法喚醒執行緒或者建立一個執行緒去執行任務,粗略的貼一下signalWork的關鍵程式碼:
private void addWorker() {
Throwable ex = null;
ForkJoinWorkerThread t = null;
try {
t = factory.newThread(this);
} catch (Throwable e) {
ex = e;
}
if (t == null) { // null or exceptional factory return
long c; // adjust counts
do {} while (!UNSAFE.compareAndSwapLong
(this, ctlOffset, c = ctl,
(((c - AC_UNIT) & AC_MASK) |
((c - TC_UNIT) & TC_MASK) |
(c & ~(AC_MASK|TC_MASK)))));
// Propagate exception if originating from an external caller
if (!tryTerminate(false) && ex != null &&
!(Thread.currentThread() instanceof ForkJoinWorkerThread))
UNSAFE.throwException(ex);
}
else
t.start();
}
最終呼叫到了這裡,來執行任務。
join方法
從文章開頭的栗子來看,join方法會阻塞當前執行緒,等待獲取任務執行的結果
//百度了這四種狀態的含義
private static final int NORMAL = -1; //NORMAL已完成
private static final int CANCELLED = -2; //CANCELLED已取消
private static final int EXCEPTIONAL = -3; //EXCEPTIONAL出現異常
private static final int SIGNAL = 1; //SIGNAL訊號
public final V join() {
//先呼叫doJoin方法判斷上面定義的四個狀態
if (doJoin() != NORMAL)
return reportResult();
else
return getRawResult();
}
join方法先呼叫doJoin方法判斷任務的狀態,看看doJoin方法,
private int doJoin() {
Thread t; ForkJoinWorkerThread w; int s; boolean completed;
//獲取當前ForkJoinWorkerThread執行緒
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
//如果狀態是小於0 也就是 -1,-2,-3 分別代表已完成、已取消、出現異常
//直接返回狀態
if ((s = status) < 0)
return s;
if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
//如果狀態為1|SIGNAL|訊號
//執行任務
try {
completed = exec();
} catch (Throwable rex) {
//出現異常,把狀態改為-3|EXCEPTIONAL|出現異常,返回
return setExceptionalCompletion(rex);
}
if (completed)
//執行成功,把狀態改為-1|NORMAL|已完成,返回
return setCompletion(NORMAL);
}
return w.joinTask(this);
}
else
return externalAwaitDone();
}
doJoin檢視任務的狀態,如果狀態是-1|NORMAL|已完成,-2|CANCELLED|已取消,-3|EXCEPTIONAL|出現異常,證明任務已經執行完畢,返回狀態位,如果狀態是 1|SIGNAL|訊號,則去執行任務,如果執行成功返回-1|NORMAL|已完成,出現異常返回-3|EXCEPTIONAL|出現異常。
再來看看返回結果的reportResult方法和getRawResult方法:
private V reportResult() {
int s; Throwable ex;
//如果狀態為-2|CANCELLED|已取消,丟擲一個CancellationException異常
if ((s = status) == CANCELLED)
throw new CancellationException();
if (s == EXCEPTIONAL && (ex = getThrowableException()) != null)
UNSAFE.throwException(ex);
//呼叫getRawResult方法返回結果
return getRawResult();
}
reportResult方法,先會判斷狀態,如果狀態為-2|CANCELLED|已取消,則丟擲一個CancellationException異常,否則呼叫getRawResult方法返回結果:
public abstract V getRawResult();
getRawResult方法在ForkJoinTask類是抽象方法,具體實現在他的兩子類中。
RecursiveAction子類:
public final Void getRawResult() { return null; }
所以說RecursiveAction子類使用於沒有返回值的任務。
RecursiveTask子類:
public final V getRawResult() {
return result;
}
RecursiveTask子類適用於有返回值的任務。
並行執行任務的Fork/Join框架是說完了。
歡迎大家來交流,指出文中一些說錯的地方,讓我加深認識。
謝謝大家!
相關文章
- Java7提供的並行執行任務框架:Fork、Join框架Java並行框架
- Fork Join 併發任務執行框架框架
- java多執行緒8:阻塞佇列與Fork/Join框架Java執行緒佇列框架
- Fork/Join框架框架
- Fork/Join 框架框架
- Java Fork/Join 框架Java框架
- laravel框架任務排程(定時執行任務)Laravel框架
- JUC之Fork/Join框架框架
- Java多執行緒並行處理任務的實現Java執行緒並行
- Java併發 -- Fork/Join框架Java框架
- C#多執行緒開發-任務並行庫04C#執行緒並行
- 180628-動態任務執行框架想法篇框架
- 實現一個併發任務執行框架框架
- laravel建立定時任務並在windows下執行LaravelWindows
- 關機提示 ”task host window任務宿主正在執行關閉任務並且正在停止已執行的任務“我是這樣解決的
- 多執行緒高併發程式設計(8) -- Fork/Join原始碼分析執行緒程式設計原始碼
- Python建立多執行緒任務並獲取每個執行緒返回值Python執行緒
- 溫故之.NET 任務並行並行
- XTask 一個擴充性極強的Android任務執行框架Android框架
- php:多程式執行任務PHP
- IDEA上執行Flink任務Idea
- 任務排程的並行演算法並行演算法
- goroutine併發執行多個任務並依次返回結果Go
- 獲取任務的執行結果
- 同步任務與非同步任務執行順序非同步
- 微任務和巨集任務哪個先執行
- job任務均不執行,手工執行報job now running
- golang runtime實現多核並行任務Golang並行
- Java併發6:阻塞佇列,Fork/Join框架Java佇列框架
- 如何避免任務重複執行
- Spark叢集和任務執行Spark
- SpringBoot執行定時任務@ScheduledSpring Boot
- 使用screen後臺執行任務
- C# 執行緒與任務C#執行緒
- .net使用Task多執行緒執行任務 .net限制執行緒數量執行緒
- PHP定時執行任務的實現PHP
- Springboot-之定時任務,啟動執行任務Spring Boot
- join、volatile、newSingleThreadLatch 實現執行緒順序執行thread執行緒
- 執行緒、開啟執行緒的兩種方式、執行緒下的Join方法、守護執行緒執行緒