溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進(jìn)行java并發(fā)FutureTask的實(shí)現(xiàn)

發(fā)布時間:2021-10-13 15:02:43 來源:億速云 閱讀:151 作者:柒染 欄目:編程語言

如何進(jìn)行java并發(fā)FutureTask的實(shí)現(xiàn),針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

概述

在使用java多線程解決問題的時候,為了提高效率,我們常常會異步處理一些計(jì)算任務(wù)并在最后異步的獲取計(jì)算結(jié)果,這個過程的實(shí)現(xiàn)離不開Future接口及其實(shí)現(xiàn)類FutureTask。FutureTask類實(shí)現(xiàn)了Runnable, Future接口,接下來我會通過源碼對該類的實(shí)現(xiàn)進(jìn)行詳解。

使用

我們先看下FutureTask中的主要方法如下,可以看出FutureTask實(shí)現(xiàn)了任務(wù)及異步結(jié)果的集合功能。看到這塊的方法,大家肯定會有疑問,Runnable任務(wù)的run方法返回空,F(xiàn)utureTask如何依靠該方法獲取線程異步執(zhí)行結(jié)果,這個問題,我們在下面給大家介紹。

//以下五個方法實(shí)現(xiàn)接口Future中方法public boolean isCancelled(); public boolean isDone(); public boolean cancel();public V get() throws InterruptedException, ExecutionException;public V get(long timeout, TimeUnit unit);//實(shí)現(xiàn)接口Runnable中方法public void run();

我們在使用中會構(gòu)造一個FutureTask對象,然后將FutureTask扔到另一個線程中執(zhí)行,而主線程繼續(xù)執(zhí)行其他業(yè)務(wù)邏輯,一段時間后主線程調(diào)用FutureTask的get方法獲取執(zhí)行結(jié)果。下面我們看一個簡單的例子:

/*** Created by yuanqiongqiong on 2019/4/9.*/public class FutureTaskTest {private static ExecutorService executorService = Executors.newFixedThreadPool(1);public static void main(String []args) {Callable callable = new AccCallable(1, 2);FutureTask futureTask = new FutureTask(callable);executorService.execute(futureTask);System.out.println("go to do other things in main thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("go back in main thread");try {int result = (int) futureTask.get();System.out.println("result is " + result);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}static class AccCallable implements Callable<Integer> {private int a;private int b;public AccCallable(int a, int b) {this.a = a;this.b = b;}@Overridepublic Integer call() throws Exception {System.out.println("acc a and b in threadId = " + Thread.currentThread().getName());return a + b;}}}

輸出結(jié)果為:

go to do other things in main threadacc a and b in threadId = pool-1-thread-1go back in main threadresult is 3

實(shí)現(xiàn)分析

在分析實(shí)現(xiàn)前,我們先想下如果讓我們實(shí)現(xiàn)一個類似FutureTask的功能,我們會如何做?因?yàn)樾枰@取執(zhí)行結(jié)果,需要一個Object對象來存執(zhí)行結(jié)果。任務(wù)執(zhí)行時間不可控性,我們需要一個變量表示執(zhí)行狀態(tài)。其他線程會調(diào)用get方法獲取結(jié)果,在沒達(dá)到超時的時候需要將線程阻塞或掛起。

因此需要一個隊(duì)列類似的結(jié)構(gòu)存儲等待該結(jié)果的線程信息,這樣在任務(wù)執(zhí)行線程完成后就可以喚醒這些阻塞或掛起的線程,得到結(jié)果。FutureTask的實(shí)際實(shí)現(xiàn)也是類似的邏輯,具體如下。

首先看下FutureTask的主要成員變量如下:

//futureTask執(zhí)行狀態(tài)private volatile int state;//具體的執(zhí)行任務(wù),會在run方法中抵用callable.call()private Callable<V> callable;//執(zhí)行結(jié)果private Object outcome; //獲取結(jié)果的等待線程節(jié)點(diǎn)private volatile WaitNode waiters;

對于執(zhí)行狀態(tài),在源碼中已經(jīng)有了非常清晰的解釋,這里我只是貼出源碼,不在進(jìn)行說明,具體如下:

/*** Possible state transitions:* NEW -> COMPLETING -> NORMAL* NEW -> COMPLETING -> EXCEPTIONAL* NEW -> CANCELLED* NEW -> INTERRUPTING -> INTERRUPTED*/private static final int NEW = 0;private static final int COMPLETING = 1;private static final int NORMAL = 2;private static final int EXCEPTIONAL = 3;private static final int CANCELLED = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED = 6;

然后我們看下FutureTask的構(gòu)造函數(shù),如下:

public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW; // ensure visibility of callable}public FutureTask(Runnable runnable, V result) {//構(gòu)造函數(shù)傳入runnable對象時調(diào)用靜態(tài)工具類Executors的方法轉(zhuǎn)換為一個callable對象this.callable = Executors.callable(runnable, result);this.state = NEW; // ensure visibility of callable}

如前所述,F(xiàn)utureTask的執(zhí)行線程中會調(diào)用其run()方法執(zhí)行任務(wù),我們看下這塊邏輯:

public void run() {//1.如果執(zhí)行狀態(tài)不是NEW或者有其他線程執(zhí)行該任務(wù),直接返回if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;//2.如果執(zhí)行狀態(tài)是NEW,即任務(wù)還沒執(zhí)行,直接調(diào)用callable.call()方法獲取執(zhí)行結(jié)果if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;//3.發(fā)生異常,更新status為EXCEPTIONAL,喚醒掛起線程setException(ex);}//4.如果結(jié)果成功返回,調(diào)用set方法將設(shè)置outcome,更改status執(zhí)行狀態(tài),喚醒掛起線程if (ran)set(result);}} finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;if (s >= INTERRUPTING)handlePossibleCancellationInterrupt(s);}}

我們看下set函數(shù)的實(shí)現(xiàn),具體看下4中的執(zhí)行:

protected void set(V v) {//將執(zhí)行狀態(tài)變更為COMPLETINGif (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//設(shè)置執(zhí)行結(jié)果outcome = v;//設(shè)置執(zhí)行狀態(tài)為NORMALUNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state//執(zhí)行完成后處理操作,具體就是遍歷阻塞鏈表,刪除鏈表節(jié)點(diǎn),并喚醒每個節(jié)點(diǎn)關(guān)聯(lián)的線程finishCompletion();}}

以上就是任務(wù)執(zhí)行線程做的邏輯,以上邏輯也回答了FutureTask如何得到執(zhí)行結(jié)果的疑問。下面我們看下用戶調(diào)用get方法獲取執(zhí)行結(jié)果時的實(shí)現(xiàn)邏輯,這個時候FutureTask可能處理各種狀態(tài),即可能沒有執(zhí)行,執(zhí)行中,已完成,發(fā)生異常等,具體如下:

public V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException {if (unit == null)throw new NullPointerException();int s = state;//執(zhí)行狀態(tài)是NEW或者COMPLETING時執(zhí)行awaitDone將線程加入等待隊(duì)列中并掛起線程if (s <= COMPLETING &&(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)throw new TimeoutException();//根據(jù)執(zhí)行狀態(tài)status進(jìn)行結(jié)果封裝return report(s);}//我理解這塊是get的核心邏輯private int awaitDone(boolean timed, long nanos)throws InterruptedException {//如果設(shè)置了超時時間,計(jì)算還有多長時間超時final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;boolean queued = false;for (;;) {//如果當(dāng)前線程被中斷,刪除等待隊(duì)列中的節(jié)點(diǎn),并拋出異常if (Thread.interrupted()) {removeWaiter(q);throw new InterruptedException();}int s = state;//如果執(zhí)行狀態(tài)已經(jīng)完成或者發(fā)生異常,直接跳出自旋返回if (s > COMPLETING) {if (q != null)q.thread = null;return s;}//如果執(zhí)行狀態(tài)是正在執(zhí)行,說明線程已經(jīng)被加入到等待隊(duì)列中,放棄cpu進(jìn)入下次循環(huán)(真正的自旋)else if (s == COMPLETING) // cannot time out yetThread.yield();//第一次進(jìn)入循環(huán),創(chuàng)建節(jié)點(diǎn)else if (q == null)q = new WaitNode();//將節(jié)點(diǎn)加入到等待隊(duì)列中,waiters相當(dāng)于頭階段,不斷將頭結(jié)點(diǎn)更新為新節(jié)點(diǎn)else if (!queued)queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);else if (timed) {//如果設(shè)置了超時時間,在進(jìn)行下次循環(huán)前查看是否已經(jīng)超時,如果超時刪除該節(jié)點(diǎn)進(jìn)行返回nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}//掛起當(dāng)前節(jié)點(diǎn)LockSupport.parkNanos(this, nanos);}elseLockSupport.park(this);}}

這里需要說明一點(diǎn),F(xiàn)utureTask中的阻塞隊(duì)列新加入的節(jié)點(diǎn)都在頭結(jié)點(diǎn)并且next指向之前的頭結(jié)點(diǎn),waitars指針總是指向新加入節(jié)點(diǎn),通過waitars可以遍歷整個等待隊(duì)列,具體截圖如下。此外等待隊(duì)列節(jié)點(diǎn)結(jié)構(gòu)很簡單成員變量只有線程引用和next指針,這里再列出器接口。

futureTask等待隊(duì)列

讀到這里,相信大家已經(jīng)對FutureTask的實(shí)現(xiàn)細(xì)節(jié)有了一定的認(rèn)識。此外,F(xiàn)utureTask沒有使用鎖而是使用Unsafe的是CAS的原子操作來解決競爭問題,減少了鎖帶來的上下文切換的開銷,提高了效率。

關(guān)于如何進(jìn)行java并發(fā)FutureTask的實(shí)現(xiàn)問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI