摘要:零前期準備文章異常啰嗦且繞彎。版本版本簡介是中默認的實現(xiàn)類,常與結(jié)合進行多線程并發(fā)操作。所以方法的主體其實就是去喚醒被阻塞的線程。本文僅為個人的學習筆記,可能存在錯誤或者表述不清的地方,有緣補充
零 前期準備 0 FBI WARNING
文章異常啰嗦且繞彎。
1 版本JDK 版本 : OpenJDK 11.0.1
IDE : idea 2018.3
2 ThreadLocal 簡介FutureTask 是 jdk 中默認的 Future 實現(xiàn)類,常與 Callable 結(jié)合進行多線程并發(fā)操作。
3 Demoimport java.util.concurrent.*; public class FutureTaskDemo { public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException { //創(chuàng)建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(1); try{ //創(chuàng)建一個要執(zhí)行的 Callable 對象 //此處其實 Runnable 對象也可以,但是通常不會那樣做 Callable一 FutureTask 的創(chuàng)建task = () -> { //休眠三秒 TimeUnit.SECONDS.sleep(3); //返回一個字符串 return "hello"; }; //用 FutureTask 對象去包裝 Callable FutureTask futureTask = new FutureTask<>(task); //此處將 FutureTask 對象丟進線程池里 pool.submit(futureTask); //注意,此處的 futureTask 本質(zhì)上是作為 Runnable 被丟進池子里的 //所以也可以用線程池的 execute(...) 方法 //pool.execute(futureTask) //還有一種更常見的執(zhí)行方式是直接使用 Thread //new Thread(futureTask).start(); //獲取結(jié)果 //注意,如果沒有獲取到的話此處會阻塞線程直到獲取到為止 String result = futureTask.get(); //還有一種限時策略的結(jié)果獲取 //超時的情況下會拋出異常 //String result = futureTask.get(1,TimeUnit.SECONDS); System.out.println(result); }finally { //關(guān)閉連接池 pool.shutdown(); } } }
回到 Demo 中的創(chuàng)建代碼:
FutureTaskfutureTask = new FutureTask<>(task);
追蹤 FutureTask 的構(gòu)造器:
//FutureTask.class public FutureTask(Callable二 runcallable) { //有效性判斷,不能為空 if (callable == null) throw new NullPointerException(); //記錄下 callable 對象 this.callable = callable; //state 是一個 int 類型的對象,是一個 //NEW = 0 this.state = NEW; }
FutureTask 本身是 Runnable 的實現(xiàn)類,其在被 ThreadPoolExecutor 或者 Thread 對象消費的時候也是被當做 Runnable 的實現(xiàn)類的。
所以其本身的核心邏輯就必然在 run() 方法中:
//FutureTask.class public void run() { //先判斷狀態(tài),如果狀態(tài)不是 NEW 就會直接返回 //RUNNER 是一個 VarHandler 類型的變量,指向了 FutureTask 中的 thread 變量,用于儲存當前的線程 //但是如果 thread 已經(jīng)不為 null,此處也會直接返回 //這兩種返回條件都意味著此 FutureTask 的 run() 方法已經(jīng)執(zhí)行過了 if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread())) return; try { //獲取 callable Callablec = callable; if (c != null && state == NEW) { V result; boolean ran; try { //執(zhí)行 callable 的業(yè)務(wù)邏輯 result = c.call(); //ran 為成功標識 ran = true; } catch (Throwable ex) { //出錯的情況下 result = null; ran = false; //不成功的情況下存入 exception setException(ex); } //如果成功的話會在此處進行操作 if (ran) set(result); } } finally { //置空 runner = null; int s = state; if (s >= INTERRUPTING) //如果此 FutreTask 的狀態(tài)是中斷狀態(tài),會在此處不斷調(diào)用 Thread.yield() 空轉(zhuǎn) handlePossibleCancellationInterrupt(s); } }
此處有兩個關(guān)鍵方法,即為 setException(...) 和 set(...):
//FutureTask.class protected void setException(Throwable t) { //用 CAS 操作比較并更新狀態(tài)值 if (STATE.compareAndSet(this, NEW, COMPLETING)) { //outcome 是一個 Object 對象,用于存儲 callable 的返回值 //此處由于報錯了,所以儲存的是錯誤對象 outcome = t; //EXCEPTIONAL = 3 STATE.setRelease(this, EXCEPTIONAL); //最后清理工作,主要用于喚醒等待線程和執(zhí)行 callable finishCompletion(); } } //FutureTask.class protected void set(V v) { //基本邏輯和 setException(...) 方法雷同,只是 STATE 和 outcome 的儲存值不同 if (STATE.compareAndSet(this, NEW, COMPLETING)) { outcome = v; STATE.setRelease(this, NORMAL); finishCompletion(); } }
再來看 finishCompletion() 方法:
//FutureTask.class private void finishCompletion() { //WaitNode 是 FutureTask 的靜態(tài)內(nèi)部類 //其本質(zhì)上是單向鏈表的節(jié)點表示類,用于存放想要獲取 Callable 的返回值但是被阻塞的線程的線程對象 for (WaitNode q; (q = waiters) != null;) { //此處使用 CAS 將 q 從 WAITERS 里去除 if (WAITERS.weakCompareAndSet(this, q, null)) { for (;;) { Thread t = q.thread; if (t != null) { //此處置空線程對象,幫助 GC q.thread = null; //喚醒線程 LockSupport.unpark(t); } //接著往下遍歷 WaitNode next = q.next; if (next == null) break; q.next = null; q = next; } break; } } //此方法是空的 done(); //置空 callable callable = null; }
之前提到過在 FutureTask 的 get(...) 方法中會阻塞線程,直到 Callable 執(zhí)行完畢并能夠獲取返回值的時候才會結(jié)束阻塞。
所以 finishCompletion() 方法的主體其實就是去喚醒被阻塞的線程。
三 get回到 Demo 中的創(chuàng)建代碼:
String result = futureTask.get();
追蹤 get() 方法:
//step 1 //FutureTask.class public V get() throws InterruptedException, ExecutionException { //此處先判斷狀態(tài)值,如果非 COMPLETING,即為還沒完成,就會調(diào)用 awaitDone(...) 方法阻塞線程 int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); //返回結(jié)果 return report(s); } //step 2 //FutureTask.class private V report(int s) throws ExecutionException { //獲取需要返回的對象 Object x = outcome; //如果是正常結(jié)束的就直接返回對象即可 if (s == NORMAL) return (V)x; //出錯的情況下,拋異常 if (s >= CANCELLED) throw new CancellationException(); throw new ExecutionException((Throwable)x); }
再來看一下阻塞線程的 awaitDone(...) 方法:
private int awaitDone(boolean timed, long nanos) throws InterruptedException { //循環(huán)的次數(shù) long startTime = 0L; //節(jié)點對象 WaitNode q = null; //鏈表隊列標識,代表該線程是否被加入鏈表中,初始為 false 代表未被加入 boolean queued = false; for (;;) { int s = state; if (s > COMPLETING) { //如果 Callable 的執(zhí)行已經(jīng)完成 if (q != null) q.thread = null; return s; }else if (s == COMPLETING) //Callable 的執(zhí)行剛剛完成,后續(xù)工作還沒做 Thread.yield(); else if (Thread.interrupted()) { //線程被中斷了,會拋出錯誤 removeWaiter(q); throw new InterruptedException(); } else if (q == null) { //進入此處的判斷證明 Callable 還未完成,所以會創(chuàng)建等待節(jié)點 //此處的 timed 傳入為 false,不會在此返回 if (timed && nanos <= 0L) return s; q = new WaitNode(); //新建節(jié)點 }else if (!queued) //queued 初始為 false,進入此處的時候會將上一個判斷條件中新建的 q 加入到鏈表的首節(jié)點中 //并且 queued 變成 true queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q); else if (timed) { //如果此操作是限時的,那么這里需要判斷時間 final long parkNanos; if (startTime == 0L) { startTime = System.nanoTime(); if (startTime == 0L) startTime = 1L; parkNanos = nanos; } else { long elapsed = System.nanoTime() - startTime; if (elapsed >= nanos) { removeWaiter(q); return state; } parkNanos = nanos - elapsed; } if (state < COMPLETING) //此處掛起線程,時間為 parkNanos //本例中傳入為 0L,所以是永久掛起 LockSupport.parkNanos(this, parkNanos); }else //永久掛起線程 LockSupport.park(this); } }四 一點嘮叨
FutureTask 和 ThreadLocal 一樣,都是 java.util.current 包中的小工具,封裝不復(fù)雜,理解即可。
本文僅為個人的學習筆記,可能存在錯誤或者表述不清的地方,有緣補充
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/73421.html
摘要:在分析它的源碼之前我們需要先了解一些預(yù)備知識。因為接口沒有返回值所以為了與兼容我們額外傳入了一個參數(shù)使得返回的對象的方法直接執(zhí)行的方法然后返回傳入的參數(shù)。 前言 系列文章目錄 FutureTask 是一個同步工具類,它實現(xiàn)了Future語義,表示了一種抽象的可生成結(jié)果的計算。在包括線程池在內(nèi)的許多工具類中都會用到,弄懂它的實現(xiàn)將有利于我們更加深入地理解Java異步操作實現(xiàn)。 在分析...
摘要:聲明了幾種方法,其中有一個就是傳入聲明了對具體的或者任務(wù)執(zhí)行進行取消查詢結(jié)果獲取等方法。事實上,是接口的一個唯一實現(xiàn)類。使用示例第一種方式是使用繼承了的線程池中的方法,將直接提交創(chuàng)建。 創(chuàng)建線程的兩種方式 直接繼承 Thread 實現(xiàn) Runnable 接口 這兩種方式都有一個缺點:在執(zhí)行完成任務(wù)之后,無法直接獲取到最后的執(zhí)行結(jié)果。如果需要獲取執(zhí)行結(jié)果,就必須通過共享變量或線程通...
摘要:本文的源碼基于。人如其名,包含了和兩部分。而將一個任務(wù)的狀態(tài)設(shè)置成終止態(tài)只有三種方法我們將在下文的源碼解析中分析這三個方法。將棧中所有掛起的線程都喚醒后,下面就是執(zhí)行方法這個方法是一個空方 前言 系列文章目錄 有了上一篇對預(yù)備知識的了解之后,分析源碼就容易多了,本篇我們就直接來看看FutureTask的源碼。 本文的源碼基于JDK1.8。 Future和Task 在深入分析源碼之前,我...
摘要:為了避免一篇文章的篇幅過長,于是一些比較大的主題就都分成幾篇來講了,這篇文章是筆者所有文章的目錄,將會持續(xù)更新,以給大家一個查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學習經(jīng)歷。因為寫作的時候發(fā)現(xiàn),為了弄懂一個知識,不得不先去了解另外一些知識,這樣以來,為了說明一個問題,就要把一系列知識都了解一遍,寫出來的文章就特別長。 為了避免一篇...
閱讀 737·2021-11-11 16:54
閱讀 3053·2021-09-26 09:55
閱讀 2002·2021-09-07 10:20
閱讀 1198·2019-08-30 10:58
閱讀 1039·2019-08-28 18:04
閱讀 698·2019-08-26 13:57
閱讀 3583·2019-08-26 13:45
閱讀 1150·2019-08-26 11:42