概述
創(chuàng)新互聯(lián)主打移動(dòng)網(wǎng)站、網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、網(wǎng)站改版、網(wǎng)絡(luò)推廣、網(wǎng)站維護(hù)、申請(qǐng)域名、等互聯(lián)網(wǎng)信息服務(wù),為各行業(yè)提供服務(wù)。在技術(shù)實(shí)力的保障下,我們?yōu)榭蛻舫兄Z穩(wěn)定,放心的服務(wù),根據(jù)網(wǎng)站的內(nèi)容與功能再?zèng)Q定采用什么樣的設(shè)計(jì)。最后,要實(shí)現(xiàn)符合網(wǎng)站需求的內(nèi)容、功能與設(shè)計(jì),我們還會(huì)規(guī)劃穩(wěn)定安全的技術(shù)方案做保障。
在使用java多線程解決問題的時(shí)候,為了提高效率,我們常常會(huì)異步處理一些計(jì)算任務(wù)并在最后異步的獲取計(jì)算結(jié)果,這個(gè)過程的實(shí)現(xiàn)離不開Future接口及其實(shí)現(xiàn)類FutureTask。FutureTask類實(shí)現(xiàn)了Runnable, Future接口,接下來我會(huì)通過源碼對(duì)該類的實(shí)現(xiàn)進(jìn)行詳解。
使用
我們先看下FutureTask中的主要方法如下,可以看出FutureTask實(shí)現(xiàn)了任務(wù)及異步結(jié)果的集合功能??吹竭@塊的方法,大家肯定會(huì)有疑問,Runnable任務(wù)的run方法返回空,F(xiàn)utureTask如何依靠該方法獲取線程異步執(zhí)行結(jié)果,這個(gè)問題,我們?cè)谙旅娼o大家介紹。
//以下五個(gè)方法實(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();
我們?cè)谑褂弥袝?huì)構(gòu)造一個(gè)FutureTask對(duì)象,然后將FutureTask扔到另一個(gè)線程中執(zhí)行,而主線程繼續(xù)執(zhí)行其他業(yè)務(wù)邏輯,一段時(shí)間后主線程調(diào)用FutureTask的get方法獲取執(zhí)行結(jié)果。下面我們看一個(gè)簡單的例子:
/** * 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; } @Override public 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 thread acc a and b in threadId = pool-1-thread-1 go back in main thread result is 3
實(shí)現(xiàn)分析
在分析實(shí)現(xiàn)前,我們先想下如果讓我們實(shí)現(xiàn)一個(gè)類似FutureTask的功能,我們會(huì)如何做?因?yàn)樾枰@取執(zhí)行結(jié)果,需要一個(gè)Object對(duì)象來存執(zhí)行結(jié)果。任務(wù)執(zhí)行時(shí)間不可控性,我們需要一個(gè)變量表示執(zhí)行狀態(tài)。其他線程會(huì)調(diào)用get方法獲取結(jié)果,在沒達(dá)到超時(shí)的時(shí)候需要將線程阻塞或掛起。
因此需要一個(gè)隊(duì)列類似的結(jié)構(gòu)存儲(chǔ)等待該結(jié)果的線程信息,這樣在任務(wù)執(zhí)行線程完成后就可以喚醒這些阻塞或掛起的線程,得到結(jié)果。FutureTask的實(shí)際實(shí)現(xiàn)也是類似的邏輯,具體如下。
首先看下FutureTask的主要成員變量如下:
//futureTask執(zhí)行狀態(tài) private volatile int state; //具體的執(zhí)行任務(wù),會(huì)在run方法中抵用callable.call() private Callable<V> callable; //執(zhí)行結(jié)果 private Object outcome; //獲取結(jié)果的等待線程節(jié)點(diǎn) private volatile WaitNode waiters;
對(duì)于執(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對(duì)象時(shí)調(diào)用靜態(tài)工具類Executors的方法轉(zhuǎn)換為一個(gè)callable對(duì)象 this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
如前所述,F(xiàn)utureTask的執(zhí)行線程中會(huì)調(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 interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
我們看下set函數(shù)的實(shí)現(xiàn),具體看下4中的執(zhí)行:
protected void set(V v) { //將執(zhí)行狀態(tài)變更為COMPLETING if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { //設(shè)置執(zhí)行結(jié)果 outcome = v; //設(shè)置執(zhí)行狀態(tài)為NORMAL UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state //執(zhí)行完成后處理操作,具體就是遍歷阻塞鏈表,刪除鏈表節(jié)點(diǎn),并喚醒每個(gè)節(jié)點(diǎn)關(guān)聯(lián)的線程 finishCompletion(); } }
以上就是任務(wù)執(zhí)行線程做的邏輯,以上邏輯也回答了FutureTask如何得到執(zhí)行結(jié)果的疑問。下面我們看下用戶調(diào)用get方法獲取執(zhí)行結(jié)果時(shí)的實(shí)現(xiàn)邏輯,這個(gè)時(shí)候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時(shí)執(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è)置了超時(shí)時(shí)間,計(jì)算還有多長時(shí)間超時(shí) 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 yet Thread.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è)置了超時(shí)時(shí)間,在進(jìn)行下次循環(huán)前查看是否已經(jīng)超時(shí),如果超時(shí)刪除該節(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); } else LockSupport.park(this); } }
這里需要說明一點(diǎn),F(xiàn)utureTask中的阻塞隊(duì)列新加入的節(jié)點(diǎn)都在頭結(jié)點(diǎn)并且next指向之前的頭結(jié)點(diǎn),waitars指針總是指向新加入節(jié)點(diǎn),通過waitars可以遍歷整個(gè)等待隊(duì)列,具體截圖如下。此外等待隊(duì)列節(jié)點(diǎn)結(jié)構(gòu)很簡單成員變量只有線程引用和next指針,這里再列出器接口。
futureTask等待隊(duì)列
讀到這里,相信大家已經(jīng)對(duì)FutureTask的實(shí)現(xiàn)細(xì)節(jié)有了一定的認(rèn)識(shí)。此外,F(xiàn)utureTask沒有使用鎖而是使用Unsafe的是CAS的原子操作來解決競(jìng)爭(zhēng)問題,減少了鎖帶來的上下文切換的開銷,提高了效率。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
分享標(biāo)題:簡談java并發(fā)FutureTask的實(shí)現(xiàn)
文章分享:http://jinyejixie.com/article40/gcsjho.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈、、App設(shè)計(jì)、建站公司、虛擬主機(jī)、網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)