您好,登錄后才能下訂單哦!
概述
在使用java多線程解決問題的時候,為了提高效率,我們常常會異步處理一些計算任務并在最后異步的獲取計算結果,這個過程的實現離不開Future接口及其實現類FutureTask。FutureTask類實現了Runnable, Future接口,接下來我會通過源碼對該類的實現進行詳解。
使用
我們先看下FutureTask中的主要方法如下,可以看出FutureTask實現了任務及異步結果的集合功能。看到這塊的方法,大家肯定會有疑問,Runnable任務的run方法返回空,FutureTask如何依靠該方法獲取線程異步執行結果,這個問題,我們在下面給大家介紹。
//以下五個方法實現接口Future中方法 public boolean isCancelled(); public boolean isDone(); public boolean cancel(); public V get() throws InterruptedException, ExecutionException; public V get(long timeout, TimeUnit unit); //實現接口Runnable中方法 public void run();
我們在使用中會構造一個FutureTask對象,然后將FutureTask扔到另一個線程中執行,而主線程繼續執行其他業務邏輯,一段時間后主線程調用FutureTask的get方法獲取執行結果。下面我們看一個簡單的例子:
/** * 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; } } }
輸出結果為:
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
實現分析
在分析實現前,我們先想下如果讓我們實現一個類似FutureTask的功能,我們會如何做?因為需要獲取執行結果,需要一個Object對象來存執行結果。任務執行時間不可控性,我們需要一個變量表示執行狀態。其他線程會調用get方法獲取結果,在沒達到超時的時候需要將線程阻塞或掛起。
因此需要一個隊列類似的結構存儲等待該結果的線程信息,這樣在任務執行線程完成后就可以喚醒這些阻塞或掛起的線程,得到結果。FutureTask的實際實現也是類似的邏輯,具體如下。
首先看下FutureTask的主要成員變量如下:
//futureTask執行狀態 private volatile int state; //具體的執行任務,會在run方法中抵用callable.call() private Callable<V> callable; //執行結果 private Object outcome; //獲取結果的等待線程節點 private volatile WaitNode waiters;
對于執行狀態,在源碼中已經有了非常清晰的解釋,這里我只是貼出源碼,不在進行說明,具體如下:
/** * 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的構造函數,如下:
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) { //構造函數傳入runnable對象時調用靜態工具類Executors的方法轉換為一個callable對象 this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
如前所述,FutureTask的執行線程中會調用其run()方法執行任務,我們看下這塊邏輯:
public void run() { //1.如果執行狀態不是NEW或者有其他線程執行該任務,直接返回 if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; //2.如果執行狀態是NEW,即任務還沒執行,直接調用callable.call()方法獲取執行結果 if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; //3.發生異常,更新status為EXCEPTIONAL,喚醒掛起線程 setException(ex); } //4.如果結果成功返回,調用set方法將設置outcome,更改status執行狀態,喚醒掛起線程 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函數的實現,具體看下4中的執行:
protected void set(V v) { //將執行狀態變更為COMPLETING if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { //設置執行結果 outcome = v; //設置執行狀態為NORMAL UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state //執行完成后處理操作,具體就是遍歷阻塞鏈表,刪除鏈表節點,并喚醒每個節點關聯的線程 finishCompletion(); } }
以上就是任務執行線程做的邏輯,以上邏輯也回答了FutureTask如何得到執行結果的疑問。下面我們看下用戶調用get方法獲取執行結果時的實現邏輯,這個時候FutureTask可能處理各種狀態,即可能沒有執行,執行中,已完成,發生異常等,具體如下:
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (unit == null) throw new NullPointerException(); int s = state; //執行狀態是NEW或者COMPLETING時執行awaitDone將線程加入等待隊列中并掛起線程 if (s <= COMPLETING && (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING) throw new TimeoutException(); //根據執行狀態status進行結果封裝 return report(s); } //我理解這塊是get的核心邏輯 private int awaitDone(boolean timed, long nanos) throws InterruptedException { //如果設置了超時時間,計算還有多長時間超時 final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; boolean queued = false; for (;;) { //如果當前線程被中斷,刪除等待隊列中的節點,并拋出異常 if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; //如果執行狀態已經完成或者發生異常,直接跳出自旋返回 if (s > COMPLETING) { if (q != null) q.thread = null; return s; } //如果執行狀態是正在執行,說明線程已經被加入到等待隊列中,放棄cpu進入下次循環(真正的自旋) else if (s == COMPLETING) // cannot time out yet Thread.yield(); //第一次進入循環,創建節點 else if (q == null) q = new WaitNode(); //將節點加入到等待隊列中,waiters相當于頭階段,不斷將頭結點更新為新節點 else if (!queued) queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { //如果設置了超時時間,在進行下次循環前查看是否已經超時,如果超時刪除該節點進行返回 nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } //掛起當前節點 LockSupport.parkNanos(this, nanos); } else LockSupport.park(this); } }
這里需要說明一點,FutureTask中的阻塞隊列新加入的節點都在頭結點并且next指向之前的頭結點,waitars指針總是指向新加入節點,通過waitars可以遍歷整個等待隊列,具體截圖如下。此外等待隊列節點結構很簡單成員變量只有線程引用和next指針,這里再列出器接口。
futureTask等待隊列
讀到這里,相信大家已經對FutureTask的實現細節有了一定的認識。此外,FutureTask沒有使用鎖而是使用Unsafe的是CAS的原子操作來解決競爭問題,減少了鎖帶來的上下文切換的開銷,提高了效率。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。