不怕難之FutureTask源碼分析

一合砂、引言

1.?FutureTask在高并發(fā)場景下能確保任務(wù)只執(zhí)行一次嗎?

2.?任務(wù)還在執(zhí)行的時候用戶調(diào)用cancel能否讓任務(wù)停止執(zhí)行?

二德绿、功能簡介

FutureTask是一種異步任務(wù)(或異步計算)岔帽,舉個栗子玫鸟,主線程的邏輯中需要使用某個值,但這個值需要負責(zé)的運算得來犀勒,那么主線程可以提前建立一個異步任務(wù)來計算這個值(在其他的線程中計算)屎飘,然后去做其他事情,當(dāng)需要這個值的時候再通過剛才建立的異步任務(wù)來獲取這個值贾费,有點并行的意思钦购,這樣可以縮短整個主線程邏輯的執(zhí)行時間。

與1.6版本不同褂萧,1.7的FutureTask不再基于AQS來構(gòu)建押桃,而是在內(nèi)部采用簡單的Treiber Stack來保存等待線程。

三导犹、前置知識

LockSupport

LockSupport是用來創(chuàng)建鎖及其他同步類的基本線程阻塞元素唱凯,它的park和 unpark能夠分別阻塞線程和解除線程阻塞。它提供了可以指定阻塞時長的park方法谎痢。park和unpark的基本接口為:

public static void park() {

? ? unsafe.park(false, 0L);

}

public static void unpark(Thread thread) {

? ? if (thread != null)

? ? ? ? unsafe.unpark(thread);

}

Unsafe

Java不能夠直接訪問操作系統(tǒng)底層磕昼,而是通過本地方法來訪問。Unsafe提供了硬件級別的原子訪問节猿,主要提供一下功能:

1. 分配釋放內(nèi)存

2. 定位某個字段的內(nèi)存位置

3. 掛起一個線程和恢復(fù)票从,更多的是通過LockSupport來訪問。park和unpark

4. CAS操作滨嘱,比較一個對象的某個位置的內(nèi)存值是否與期望值一致峰鄙,一致則更新對應(yīng)值,此更新是不可中斷的太雨。主要方法是compareAndSwap*

并發(fā)工具三板斧

狀態(tài)吟榴,隊列,CAS

四囊扳、源碼分析

1. FutureTask介紹

FutureTask是一種可以取消的異步的計算任務(wù)煤墙。它的計算是通過Callable實現(xiàn)的,可以把它理解為是可以返回結(jié)果的Runnable宪拥。

使用FutureTask的優(yōu)勢有:

可以獲取線程執(zhí)行后的返回結(jié)果仿野;

提供了超時控制功能。

它實現(xiàn)了Runnable接口和Future接口:


2.?FutureTask的狀態(tài)

在FutureTask中她君,狀態(tài)是由state屬性來表示的脚作,不出所料,它是volatile類型的,確保了不同線程對它修改的可見性:

private volatile int state;

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;

狀態(tài)轉(zhuǎn)換路徑


棧結(jié)構(gòu)



run方法

public void run(){

/*

? ? * 首先判斷狀態(tài)球涛,如果不是初始狀態(tài)劣针,說明任務(wù)已經(jīng)被執(zhí)行或取消;

? ? * runner是FutureTask的一個屬性亿扁,用于保存執(zhí)行任務(wù)的線程捺典,

? ? * 如果不為空則表示已經(jīng)有線程正在執(zhí)行,這里用CAS來設(shè)置从祝,失敗則返回襟己。

? ? */

if(state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))

?return;

try{

? ? ? ? Callable<V> c = callable;

? ? ? ? ? // 只有初始狀態(tài)才會執(zhí)行

? ? ? ? ?if(c !=null&& state == NEW) {

? ? ? ? ? ? V result;

? ? ? ? ? ?boolean ran;

try{

? ? ? ? ? ? ? ?// 執(zhí)行任務(wù)

? ? ? ? ? ? ? ? result = c.call();

? ? ? ? ? ? ? ?// 如果沒出現(xiàn)異常牍陌,則說明執(zhí)行成功了

? ? ? ? ? ? ? ?ran =true;

}catch(Throwable ex) {

result =null;

ran =false;

// 設(shè)置異常

setException(ex);

? }

// 如果執(zhí)行成功擎浴,則設(shè)置返回結(jié)果

if(ran)

? ? ? ? ? ? ? ? set(result);

? ? ? ? }

}finally{

// runner must be non-null until state is settled to

// prevent concurrent calls to run()

// 無論是否執(zhí)行成功,把runner設(shè)置為null

runner =null;

// state must be re-read after nulling runner to prevent

// leaked interrupts

ints = state;

// 如果被中斷毒涧,則說明調(diào)用的cancel(true)贮预,

// 這里要保證在cancel方法中把state設(shè)置為INTERRUPTED

// 否則可能在cancel方法中還沒執(zhí)行中斷,造成中斷的泄露

if(s >= INTERRUPTING)

? ? ? ? ? ? handlePossibleCancellationInterrupt(s);

? ? }

}

run方法總結(jié)

校驗當(dāng)前任務(wù)狀態(tài)是否為NEW以及runner是否已賦值契讲。這一步是防止任務(wù)被取消仿吞。

double-check任務(wù)狀態(tài)state

執(zhí)行業(yè)務(wù)邏輯,也就是c.call()方法被執(zhí)行

如果業(yè)務(wù)邏輯異常捡偏,則調(diào)用setException方法將異常對象賦給outcome唤冈,并且更新state值

如果業(yè)務(wù)正常,則調(diào)用set方法將執(zhí)行結(jié)果賦給outcome霹琼,并且更新state值

awaitDone方法

//? 第一次循環(huán):創(chuàng)建棧頭結(jié)點

// 第二次循環(huán): 入棧操作

// 第三次循環(huán): 開始阻塞务傲,等待通知

//? 隊列針對一個FutureTask示例

// 一個FutureTask示例的多個線程凉当,依次入棧枣申,通過next節(jié)點鏈接,后面再依次喚醒

private int awaitDone(boolean timed, long nanos)throws InterruptedException {

// 計算到期時間

? ? final long deadline = timed ? System.nanoTime() + nanos :0L;

? ? WaitNode q =null;

? ? boolean queued =false;

? ? for (; ; ) {

// 如果被中斷闽巩,刪除節(jié)點缸匪,拋出異常

? ? ? ? if (Thread.interrupted()) {

? ? ? ? ? ? removeWaiter(q);

? ? ? ? ? ? throw new InterruptedException();

? ? ? ? }

? ? ? ?int s = state;

? ? ? ? // 如果任務(wù)執(zhí)行完畢并且設(shè)置了最終狀態(tài)或者被取消被环,則返回

? ? ? ? if (s > COMPLETING) {

? ? ? ? ? if (q !=null)

? ? ? ? ? q.thread =null;

? ? ? ? ? ? return s;

? ? ? ? }

// s == COMPLETING時通過Thread.yield();讓步其他線程執(zhí)行,

// 主要是為了讓狀態(tài)改變

? ? ? ? else if (s == COMPLETING)// cannot time out yet

? ? ? ? ? ? Thread.yield(); // 主動讓出CPU

? ? ? ? ? ? // 創(chuàng)建一個WaitNode

? ? ? ? else if (q ==null)

? ? ? ? ? q =new WaitNode();? ?// 第一次頭節(jié)點

? ? ? ? ? ? // CAS設(shè)置棧頂節(jié)點

? ? ? ? else if (!queued)

? ? ? ? ?queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);? // 入棧操作

? ? ? ? ? ? // 如果設(shè)置了超時模孩,則計算是否已經(jīng)到了開始設(shè)置的到期時間

? ? ? ? else if (timed) {

? ? ? ? ? ? ?nanos = deadline - System.nanoTime();

? ? ? ? ? ? // 如果已經(jīng)到了到期時間,刪除節(jié)點贮缅,返回狀態(tài)

? ? ? ? ? ? if (nanos <=0L) {

? ? ? ? ? ? ? removeWaiter(q);

? ? ? ? ? ? ? ? return state;

? ? ? ? ? ? }

? ? ? ? ? ? // 阻塞到到期時間

? ? ? ? ? ? LockSupport.parkNanos(this, nanos);

? ? ? ? }

? ? ? ? // 如果沒有設(shè)置超時榨咐,會一直阻塞,直到被中斷或者被喚醒

? ? ? ? else

? ? ? ? ? ? LockSupport.park(this);

? ? }

}

awaitDone方法總結(jié)

計算deadline谴供,也就是到某個時間點后如果還沒有返回結(jié)果块茁,那么就超時了。

進入自旋,也就是死循環(huán)数焊。

首先判斷是否響應(yīng)線程中斷永淌。對于線程中斷的響應(yīng)往往會放在線程進入阻塞之前,這里也印證了這一點佩耳。

判斷state值遂蛀,如果>COMPLETING表明任務(wù)已經(jīng)取消或者已經(jīng)執(zhí)行完畢,就可以直接返回了干厚。

如果任務(wù)還在執(zhí)行李滴,則為當(dāng)前線程初始化一個等待節(jié)點WaitNode,入等待隊列萍诱。這里和AQS的等待隊列類似悬嗓,只不過Node只關(guān)聯(lián)線程,而沒有狀態(tài)裕坊。AQS里面的等待節(jié)點是有狀態(tài)的包竹。

計算nanos,判斷是否已經(jīng)超時籍凝。如果已經(jīng)超時周瞎,則移除所有等待節(jié)點,直接返回state饵蒂。超時的話声诸,state的值仍然還是COMPLETING。

如果還未超時退盯,就通過LockSupprot類提供的方法在指定時間內(nèi)掛起當(dāng)前線程彼乌,等待任務(wù)線程喚醒或者超時喚醒。

入棧示意圖



五渊迁、總結(jié)

FutureTask是線程安全的慰照,在多線程下任務(wù)也只會被執(zhí)行一次;

注意在執(zhí)行時各種狀態(tài)的切換琉朽;

get方法調(diào)用時毒租,如果任務(wù)沒有結(jié)束,要阻塞當(dāng)前線程箱叁,法阻塞的線程會保存在一個Treiber Stack中墅垮;

get方法超時功能如果超時未獲取成功,會拋出TimeoutException耕漱;

注意在取消時的線程中斷算色,在run方法中一定要保證結(jié)束時的狀態(tài)是INTERRUPTED,否則在cancel方法中可能沒有執(zhí)行interrupt螟够,造成中斷的泄露灾梦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斥废,更是在濱河造成了極大的恐慌椒楣,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牡肉,死亡現(xiàn)場離奇詭異捧灰,居然都是意外死亡,警方通過查閱死者的電腦和手機统锤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門毛俏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饲窿,你說我怎么就攤上這事煌寇。” “怎么了逾雄?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵阀溶,是天一觀的道長。 經(jīng)常有香客問我鸦泳,道長银锻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任做鹰,我火速辦了婚禮击纬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钾麸。我一直安慰自己更振,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布饭尝。 她就那樣靜靜地躺著肯腕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芋肠。 梳的紋絲不亂的頭發(fā)上乎芳,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天遵蚜,我揣著相機與錄音帖池,去河邊找鬼。 笑死吭净,一個胖子當(dāng)著我的面吹牛睡汹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寂殉,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼囚巴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起彤叉,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庶柿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秽浇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浮庐,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年柬焕,在試婚紗的時候發(fā)現(xiàn)自己被綠了审残。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡斑举,死狀恐怖搅轿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情富玷,我是刑警寧澤璧坟,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站赎懦,受9級特大地震影響沸柔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铲敛,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一褐澎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伐蒋,春花似錦工三、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至焙畔,卻和暖如春掸读,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宏多。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工儿惫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伸但。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓肾请,卻偏偏與公主長得像,于是被迫代替她去往敵國和親更胖。 傳聞我的和親對象是個殘疾皇子铛铁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容