java并發(fā)編程之FutureTask

引言

FutureTask實(shí)現(xiàn)了接口Future,同F(xiàn)uture一樣,代表異步計(jì)算的結(jié)果泉沾。當(dāng)然,F(xiàn)utureTask除了實(shí)現(xiàn)Future接口之外妇押,還實(shí)現(xiàn)了Runnable接口跷究,所以,F(xiàn)utureTask既可以由Executor來(lái)調(diào)度執(zhí)行敲霍,也可以由調(diào)度線程調(diào)用FutureTask.run()直接執(zhí)行俊马。

FutureTask狀態(tài)

根據(jù)FutureTask的run方法是否被執(zhí)行以及是否被執(zhí)行完成,F(xiàn)utureTask有3種狀態(tài):

  1. 未啟動(dòng):run方法被執(zhí)行前肩杈,F(xiàn)utureTask處于未啟動(dòng)狀態(tài)柴我;

  2. 已啟動(dòng):run方法被執(zhí)行的過(guò)程中,F(xiàn)utureTask處于已啟動(dòng)狀態(tài)扩然;

  3. 已完成:run方法執(zhí)行完成后正常結(jié)束艘儒,或者被取消,或者是執(zhí)行過(guò)程中拋出異常導(dǎo)致的異常結(jié)束,F(xiàn)utureTask處于已完成狀態(tài)彤悔。

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

FutureTask狀態(tài)轉(zhuǎn)換可以總結(jié)為下圖:


FutureTask狀態(tài)轉(zhuǎn)換圖
  • 當(dāng)FutureTask處于未啟動(dòng)或者是已啟動(dòng)狀態(tài)時(shí)嘉抓,此時(shí)還未得到線程執(zhí)行結(jié)果,調(diào)用FutureTask.get方法會(huì)導(dǎo)致線程阻塞晕窑;

  • 當(dāng)FutureTask處于已完成狀態(tài)時(shí)抑片,此時(shí)已經(jīng)得到線程執(zhí)行結(jié)果,調(diào)用FutureTask.get方法會(huì)立即返回線程執(zhí)行結(jié)果杨赤;

  • 當(dāng)FutureTask處于未啟動(dòng)狀態(tài)時(shí)敞斋,調(diào)用FutureTask.cancel方法將會(huì)導(dǎo)致該task永遠(yuǎn)不會(huì)被執(zhí)行;

  • 當(dāng)FutureTask處于啟動(dòng)狀態(tài)時(shí)疾牲,調(diào)用FutureTask.cancel方法將會(huì)中斷該任務(wù)的執(zhí)行植捎,至于會(huì)不會(huì)對(duì)任務(wù)產(chǎn)生影響由cancel方法的入?yún)Q定;

  • 當(dāng)FutureTask處于已完成狀態(tài)阳柔,調(diào)用FutureTask.cancel方法返回false焰枢。

接下來(lái)就以run、get和cancel方法為切入點(diǎn)分析FutureTask具體實(shí)現(xiàn)舌剂。

FutureTask源碼分析

在開(kāi)始分析源碼之前济锄,我們先來(lái)看看FutureTask的成員變量:


成員變量
  1. state:記錄task狀態(tài),可取值為0~6霍转;

  2. callable:task實(shí)際載體荐绝,run方法實(shí)際調(diào)用callable.call();

  3. outcome:線程執(zhí)行任務(wù)結(jié)束后的返回結(jié)果避消;

  4. runner:記錄執(zhí)行task的線程低滩;

  5. waiters:等待task執(zhí)行結(jié)果的線程隊(duì)列。

構(gòu)造方法
構(gòu)造方法

FutureTask提供兩個(gè)構(gòu)造方法來(lái)封裝Callable和Runnable岩喷,當(dāng)構(gòu)造方法傳入?yún)?shù)為Runnable恕沫,會(huì)通過(guò)Executors.callable方法將其轉(zhuǎn)換成Callable。


Executors.callable方法實(shí)現(xiàn)
get方法實(shí)現(xiàn)

FutureTask提供帶超時(shí)時(shí)間的get和不到超時(shí)時(shí)間的get:


get方法實(shí)現(xiàn)

對(duì)比帶超時(shí)時(shí)間和不帶超時(shí)時(shí)間的get方法實(shí)現(xiàn)纱意,最為重要的實(shí)現(xiàn)就是等待直到task狀態(tài)變?yōu)橐淹瓿蔂顟B(tài)或者等待時(shí)間超過(guò)超時(shí)時(shí)間昏兆,對(duì)應(yīng)到源碼就是加紅框的awaitDone方法。接下來(lái)我們來(lái)具體分析一下awaitDone方法到底是如何來(lái)實(shí)現(xiàn)線程阻塞等待的妇穴。

awaitDone方法實(shí)現(xiàn)

awaitDone實(shí)現(xiàn)

具體的執(zhí)行流程如下:

  1. 計(jì)算等待時(shí)間deadline,如果是帶超時(shí)時(shí)間的get隶债,deadline = 當(dāng)前時(shí)間 + 等待時(shí)間腾它,如果是不帶超時(shí)時(shí)間的get,deadline = 0死讹;

  2. 判斷線程是否中斷瞒滴,如果線程中斷,將當(dāng)前線程從等待隊(duì)列waiters中移除,拋出中斷異常妓忍,否則虏两,跳轉(zhuǎn)到步驟3;

  3. 獲取task狀態(tài)state:

  • 如果task狀態(tài)為已完成狀態(tài)世剖,將等待線程節(jié)點(diǎn)的線程置為null定罢,返回state;

  • 如果task狀態(tài)為正在執(zhí)行旁瘫,調(diào)用Thread.yield()將線程從執(zhí)行狀態(tài)變?yōu)榭蓤?zhí)行狀態(tài)祖凫;

  • 否則,跳轉(zhuǎn)到步驟4酬凳;

  1. 如果等待線程節(jié)點(diǎn)q為null惠况,初始化等待線程節(jié)點(diǎn)q,否則宁仔,跳轉(zhuǎn)到步驟5稠屠;

  2. 如果當(dāng)前等待線程節(jié)點(diǎn)q還未成功進(jìn)入等待隊(duì)列waiters,進(jìn)入線程等待隊(duì)列翎苫,否則权埠,跳轉(zhuǎn)到步驟6;

  3. 判斷是否是帶超時(shí)時(shí)間的get:

  • 如果是帶超時(shí)時(shí)間get拉队,判斷當(dāng)前是否超時(shí)弊知,如果已經(jīng)超時(shí),將當(dāng)前等待節(jié)點(diǎn)q從waiters中移出粱快,返回task狀態(tài)state秩彤,如果還未超時(shí),調(diào)用LockSupport.parkNanos方法阻塞當(dāng)前線程事哭;

  • 否則漫雷,跳轉(zhuǎn)到步驟7;

  1. 調(diào)用LockSupport.park方法鳍咱,阻塞當(dāng)前線程降盹,然后跳轉(zhuǎn)到步驟2。

從get方法整個(gè)流程可以看出:

  • FutureTask維護(hù)一個(gè)等待線程隊(duì)列waiters谤辜,如果task還未執(zhí)行完畢蓄坏,調(diào)用get方法的線程會(huì)先進(jìn)入等待隊(duì)列自旋等待;

  • awaitDone方法其實(shí)是個(gè)死循環(huán)丑念,直到task狀態(tài)變?yōu)橐淹瓿蔂顟B(tài)或者等待時(shí)間超過(guò)超時(shí)時(shí)間或者線程中斷才會(huì)跳出循環(huán)涡戳,程序結(jié)束;

  • 為了節(jié)省開(kāi)銷脯倚,線程不會(huì)一直自旋等待渔彰,而是會(huì)阻塞嵌屎,使用LockSupport的park系列方法實(shí)現(xiàn)線程阻塞;

run方法實(shí)現(xiàn)
run方法實(shí)現(xiàn)

具體執(zhí)行流程如下:

  1. 判斷task狀態(tài)恍涂,如果task還未執(zhí)行宝惰,跳轉(zhuǎn)到步驟2,否則再沧,返回尼夺,程序結(jié)束;

  2. 通過(guò)CAS設(shè)置執(zhí)行task的線程产园,設(shè)置成功汞斧,跳轉(zhuǎn)到步驟3,否則什燕,返回粘勒,程序結(jié)束;

  3. 執(zhí)行callable.call方法屎即,調(diào)用set方法設(shè)置call方法返回結(jié)果以及task狀態(tài)庙睡;

  4. 設(shè)置當(dāng)前運(yùn)行當(dāng)前task的線程為null;

  5. 判斷當(dāng)前task狀態(tài)技俐,如果task狀態(tài)為正在中斷或者已中斷乘陪,調(diào)用Thread.yield()將線程從執(zhí)行狀態(tài)變?yōu)榭蓤?zhí)行狀態(tài)。

set方法實(shí)現(xiàn)

set方法實(shí)現(xiàn)

set方法主要干了這兩件事:

  1. 設(shè)置返回結(jié)果outcome以及task狀態(tài)state雕擂;

  2. 調(diào)用finishCompletion方法操作等待隊(duì)列waiters中的等待線程啡邑。

finishCompletion實(shí)現(xiàn)

finishCompletion實(shí)現(xiàn)

整個(gè)finishCompletion方法清除和喚醒了等待隊(duì)列中的等待線程,調(diào)用get方法被阻塞的線程也就是在這里調(diào)用LockSupport.unpark方法被喚醒的井赌。

cancel方法實(shí)現(xiàn)
cancel方法實(shí)現(xiàn)
  1. 判斷task狀態(tài)谤逼,如果不為未啟動(dòng)狀態(tài),返回false仇穗,程序結(jié)束流部,否則,跳轉(zhuǎn)到步驟2纹坐;

  2. 判斷入?yún)ayInterruptIfRunning:

  • true:CSA設(shè)置state為正在中斷枝冀,設(shè)置失敗返回false,否則中斷正在運(yùn)行task的線程耘子,CAS設(shè)置state為已中斷果漾;

  • false:CSA設(shè)置state為已取消,設(shè)置失敗返回false谷誓,需要注意的是跨晴,正在運(yùn)行task的線程是不會(huì)中斷的,換句話說(shuō)片林,入?yún)閒alse時(shí)不會(huì)對(duì)task的執(zhí)行有任何影響。

注:根據(jù)代碼實(shí)現(xiàn):
- 處于啟動(dòng)狀態(tài)的task,調(diào)用cancel方法是否會(huì)對(duì)task的執(zhí)行有所影響完全依賴于cancel方法的入?yún)⒎逊猓瑃rue時(shí)會(huì)有影響焕妙,false時(shí)不會(huì)有影響;
- 處于未啟動(dòng)狀態(tài)的task弓摘,調(diào)用cancel方法后焚鹊,該task將不會(huì)再被執(zhí)行。

  1. 調(diào)用finishCompletion方法清除和喚醒等待隊(duì)列waiters中的等待線程韧献,返回true末患,程序結(jié)束。

從get锤窑、run璧针、cancel方法的實(shí)現(xiàn),F(xiàn)utureTask的線程等待與喚醒可以總結(jié)為下圖:


FutureTask線程等待喚醒

后記

到這里為止渊啰,F(xiàn)utureTask的源碼就分析就結(jié)束了探橱。做一個(gè)簡(jiǎn)短的總結(jié):

  1. FutureTask是通過(guò)LockSupport來(lái)阻塞線程、喚醒線程绘证;

  2. 對(duì)于多線程訪問(wèn)成員變量waiters隧膏、state,都采用CAS來(lái)操作嚷那;

總的來(lái)說(shuō)胞枕,F(xiàn)utureTask是一個(gè)非常好的CAS和LockSupport搭配使用的例子。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末魏宽,一起剝皮案震驚了整個(gè)濱河市腐泻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湖员,老刑警劉巖贫悄,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異娘摔,居然都是意外死亡窄坦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)凳寺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鸭津,“玉大人,你說(shuō)我怎么就攤上這事肠缨∧媲鳎” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵晒奕,是天一觀的道長(zhǎng)闻书。 經(jīng)常有香客問(wèn)我名斟,道長(zhǎng),這世上最難降的妖魔是什么魄眉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任砰盐,我火速辦了婚禮,結(jié)果婚禮上坑律,老公的妹妹穿的比我還像新娘岩梳。我一直安慰自己,他們只是感情好晃择,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布冀值。 她就那樣靜靜地躺著,像睡著了一般宫屠。 火紅的嫁衣襯著肌膚如雪列疗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天激况,我揣著相機(jī)與錄音作彤,去河邊找鬼。 笑死乌逐,一個(gè)胖子當(dāng)著我的面吹牛竭讳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浙踢,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼绢慢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了洛波?” 一聲冷哼從身側(cè)響起胰舆,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹬挤,沒(méi)想到半個(gè)月后缚窿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焰扳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年倦零,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吨悍。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扫茅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出育瓜,到底是詐尸還是另有隱情葫隙,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布躏仇,位于F島的核電站恋脚,受9級(jí)特大地震影響腺办,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜慧起,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一菇晃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚓挤,春花似錦、人聲如沸驻子。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)崇呵。三九已至缤剧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間域慷,已是汗流浹背荒辕。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犹褒,地道東北人抵窒。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像叠骑,于是被迫代替她去往敵國(guó)和親李皇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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