Java異步編程——深入源碼分析FutureTask

Java的異步編程是一項非常常用的多線程技術(shù)。

之前通過源碼詳細分析了ThreadPoolExecutor《你真的懂ThreadPoolExecutor線程池技術(shù)嗎襟企?看了源碼你會有全新的認識》。通過創(chuàng)建一個ThreadPoolExecutor,往里面丟任務就可以實現(xiàn)多線程異步執(zhí)行了扬绪。

但之前的任務主要傾向于線程池荠呐,并沒有講到異步編程方面的內(nèi)容赛蔫。本文將通過介紹Executor+Future框架(FutureTask是實現(xiàn)的核心),來深入了解下Java的異步編程泥张。

萬事從示例開始呵恢,我們先通過示例Demo有一個直觀的印象,再深入去了解概念與原理媚创。

使用示例

Demo:



使用上比較簡單渗钉,

運行結(jié)果:

任務1異步執(zhí)行:0
任務2異步執(zhí)行:0
任務2異步執(zhí)行:1
...
任務2異步執(zhí)行:45
同步代碼
任務2異步執(zhí)行:24
...
任務1異步執(zhí)行:199
任務1:執(zhí)行完成
...
任務2異步執(zhí)行:199
任務2:執(zhí)行完成

假若你多次執(zhí)行這個程序,會發(fā)現(xiàn)結(jié)果大大的不一樣,因為兩個任務和同步代碼是異步由多條線程執(zhí)行的鳄橘,打印的結(jié)果當然是隨機的声离。

回顧這個Demo做了什么,

  1. 構(gòu)建了一個線程池
  2. 往線程池里面丟兩個需要執(zhí)行的任務
  3. 最后獲取這兩個任務的結(jié)果

其中第二點是異步執(zhí)行兩個任務瘫怜,這兩個任務和主線程分別是用了三個線程并發(fā)執(zhí)行的术徊,第三點是在主線程中同步等待兩個任務的結(jié)果。

很容易看出來鲸湃,異步編程的好處就在于可以讓不相干的任務異步執(zhí)行赠涮,不阻塞主線程。若是主線程需要異步執(zhí)行的結(jié)果暗挑,此時再去等待結(jié)果會更加高效笋除,提高程序的執(zhí)行效率。

下面來看看整個流程的實現(xiàn)原理窿祥。

源碼分析

一般在實際項目中株憾,都會有配置有自己的線程池,建議大家在用異步編程時晒衩,配置一個專用的線程池嗤瞎,做好線程隔離,避免異步線程影響到其他模塊的工作听系。Demo中為了方便贝奇,直接調(diào)用Exectors的方法生成一個臨時的線程池,日常不建議使用靠胜。

我們從這個ExecutorService.submit()方法入手掉瞳,看看整體實現(xiàn)。


ExecutorService.submit()定義一個接口浪漠。這個接口接收一個Callable參數(shù)(執(zhí)行的任務)陕习,返回一個Future(計算結(jié)果)。

Callable址愿,相當于一個需要執(zhí)行的任務该镣。它不接收任何參數(shù),可以返回結(jié)果响谓,可以拋出異常损合。相類似的還有Runnable,它也是不接收娘纷,不同點在于它不返回結(jié)果嫁审,也不拋異常,異常需要在任務內(nèi)部處理赖晶÷墒剩總結(jié)來說Callable更像一個方法的調(diào)用,Runnable則是一個不需要理會結(jié)果的調(diào)用。在JDK 8以后擦耀,它們都可以通過Lamda表達式寫法去替代內(nèi)部類的寫法(詳見Demo)棉圈。

Future,一個異步計算的結(jié)果眷蜓。調(diào)用get()方法可以得到對應的計算結(jié)果分瘾,如果調(diào)用時沒有異步計算完,會阻塞等待計算的結(jié)果吁系。同時它還提供方法可以嘗試取消任務的執(zhí)行德召。

看回ExecutorService.submit()的實現(xiàn),代碼在實現(xiàn)類AbstractExecutorService中汽纤。


除了它接口的實現(xiàn)上岗,還提供了兩種變形。原來接口只接收Callable參數(shù)蕴坪,實現(xiàn)類中還新增了接收Runnable參數(shù)的肴掷。

如果看過之前寫的《你真的懂ThreadPoolExecutor線程池技術(shù)嗎?看了源碼你會有全新的認識》背传,應該了解ThreadPoolExecutor執(zhí)行任務是可以調(diào)用execute()方法的呆瞻。而這里面submit()方法則是為Callable/Runnable加多一層FutureTask,從而
使執(zhí)行結(jié)果有一個存放的地方径玖,同時也添加一個可以取消的功能痴脾。原本的execute()只能執(zhí)行任務,不會返回結(jié)果的梳星,具體實現(xiàn)原理可以看看之前的文章分析赞赖。

FutureTaskRunnableFuture的實現(xiàn)。而RunnableFuture是繼承FutureRunnable接口的冤灾,定義run()接口前域。


因為FutureTaskrun()接口,所以可以直接用一個Callable/Runnable創(chuàng)建一個FutureTask單獨執(zhí)行韵吨。但這樣并沒有異步的效果话侄,因為沒有啟用新的線程去跑,而是在原來的線程阻塞執(zhí)行的学赛。

到這里我們清楚知道了,submit()方法重點是利用Callable/Runnable創(chuàng)建一個FutureTask吞杭,然后多線程執(zhí)行run()方法盏浇,達到異步處理并且得到結(jié)果的效果。而FutureTask的重點則是run()方法如何持有保存計算的結(jié)果芽狗。

FutureTask.run()


首先判斷futureTask對象的state狀態(tài)绢掰,如果不是NEW的話,證明已經(jīng)開始運行過了,則退出執(zhí)行滴劲。同時futureTask對象通過CAS攻晒,把當前線程賦值給變量runner(是Thread類型,說明對象使用哪個線程執(zhí)行的)班挖,如果CAS失敗則退出鲁捏。

外層try{}代碼塊中,對callable判空和state狀態(tài)必須是NEW萧芙。內(nèi)層try{}代碼真正調(diào)用callable给梅,開始執(zhí)行任務。若執(zhí)行成功双揪,則把ran變量設為true动羽,保存結(jié)果在result變量中,證明已跑成功過了渔期;若拋異常了运吓,則設為false,result為空疯趟,并且調(diào)用setException()保存異常拘哨。最后如果ran為true的話,則調(diào)用set()保存result結(jié)果迅办。

看下setException()set()的實現(xiàn)宅静。


兩者的基本流程一樣,CAS置換狀態(tài)站欺,保存結(jié)果在outcome變量道中姨夹,但setException()保存的結(jié)果類型固定是Throwable。另外一個不同在于最終state狀態(tài)矾策,一個是EXCEPTION磷账,一個是NORMAL。

這兩個方法最后都調(diào)用了finishCompletion()贾虽。這個方法主要是配合線程池喚醒下一個任務逃糟。

FutureTask.get()

從上面run()方法得知,最后執(zhí)行的結(jié)果放在了outcome變量中蓬豁。那最終怎么從其中取出結(jié)果來绰咽,我們來看看get()方法。


從源碼可知地粪,get()方法分兩步取募。第一步,先判斷狀態(tài)蟆技,如果計算為完成玩敏,則需要阻塞地等待完成斗忌。第二步,如果完成了旺聚,則調(diào)用report()方法獲取結(jié)果并返回织阳。

先看看awaitDone()阻塞等待完成。該方法可以選用超時功能砰粹。


在自旋的for()循環(huán)中唧躲,

  • 先判斷是否線程被中斷,中斷的話拋異常退出伸眶。
  • 然后開始判斷運行的state值惊窖,如果state大于COMPLETING,證明計算已經(jīng)是終態(tài)了厘贼,此時返回終態(tài)變量界酒。
  • state等于COMPLETING,證明已經(jīng)開始計算嘴秸,并且還在計算中毁欣。此時為了避免過多的CPU時間放在這個for循環(huán)的自旋上,程序執(zhí)行Thread.yield()岳掐,把線程從運行態(tài)降為就緒態(tài)凭疮,讓出CPU時間。
  • 若以上狀態(tài)都不是串述,則證明stateNEW执解,還沒開始執(zhí)行。那么程序在當前循環(huán)現(xiàn)在會新增一個WaitNode纲酗,在下一個循環(huán)里面調(diào)用LockSupport.park()把當前線程阻塞衰腌。當run()方法結(jié)束的時候,會再次喚醒此線程觅赊,避免自旋消耗CPU時間右蕊。
  • 如果選用了超時功能,在阻塞和自旋過程中超時了吮螺,則會返回當前超時的狀態(tài)饶囚。

第二步的report()方法比較簡單。

  • 如果狀態(tài)是NORMAL鸠补,正常結(jié)束的話萝风,則把outcome變量返回;
  • 如果是取消或者中斷狀態(tài)的紫岩,則拋出取消異常闹丐;
  • 如果是EXCEPTION,則把outcome當作異常拋出(之前setException()保存的類型就是Throwable)被因。從而整個get()會有一個異常拋出卿拴。

總結(jié)

至此我們已經(jīng)比較完整地了解Executor+Future的框架原理了,而FutureTask則是該框架的主要實現(xiàn)梨与。下面總結(jié)下要點

  1. Executor.sumbit()方法異步執(zhí)行一個任務堕花,并且返回一個Future結(jié)果。
  2. submit()的原理是利用Callable創(chuàng)建一個FutureTask對象粥鞋,然后執(zhí)行對象的run()方法缘挽,把結(jié)果保存在outcome中。
  3. 調(diào)用get()獲取outcome時呻粹,如果任務未完成壕曼,會阻塞線程,等待執(zhí)行完畢等浊。
  4. 異常和正常結(jié)果都放在outcome中腮郊,調(diào)用get()獲取結(jié)果或拋出異常。

更多技術(shù)文章筹燕、精彩干貨轧飞,請關(guān)注
博客:zackku.com
微信公眾號:Zack說碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撒踪,隨后出現(xiàn)的幾起案子过咬,更是在濱河造成了極大的恐慌,老刑警劉巖制妄,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掸绞,死亡現(xiàn)場離奇詭異,居然都是意外死亡耕捞,警方通過查閱死者的電腦和手機衔掸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砸脊,“玉大人具篇,你說我怎么就攤上這事×韫。” “怎么了驱显?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瞳抓。 經(jīng)常有香客問我埃疫,道長,這世上最難降的妖魔是什么孩哑? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任栓霜,我火速辦了婚禮,結(jié)果婚禮上横蜒,老公的妹妹穿的比我還像新娘胳蛮。我一直安慰自己销凑,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布仅炊。 她就那樣靜靜地躺著斗幼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抚垄。 梳的紋絲不亂的頭發(fā)上蜕窿,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音呆馁,去河邊找鬼桐经。 笑死,一個胖子當著我的面吹牛浙滤,可吹牛的內(nèi)容都是我干的阴挣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瓷叫,長吁一口氣:“原來是場噩夢啊……” “哼屯吊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摹菠,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盒卸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后次氨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔽介,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年煮寡,在試婚紗的時候發(fā)現(xiàn)自己被綠了虹蓄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡幸撕,死狀恐怖薇组,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坐儿,我是刑警寧澤律胀,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站貌矿,受9級特大地震影響炭菌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逛漫,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一黑低、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酌毡,春花似錦克握、人聲如沸蕾管。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娇掏。三九已至,卻和暖如春勋眯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背下梢。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工客蹋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孽江。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓讶坯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親岗屏。 傳聞我的和親對象是個殘疾皇子辆琅,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355