Java的異步編程是一項很是經常使用的多線程技術。java
以前經過源碼詳細分析了ThreadPoolExecutor《你真的懂ThreadPoolExecutor線程池技術嗎?看了源碼你會有全新的認識》。經過建立一個ThreadPoolExecutor,往裏面丟任務就能夠實現多線程異步執行了。編程
但以前的任務主要傾向於線程池,並無講到異步編程方面的內容。本文將經過介紹Executor+Future框架(FutureTask是實現的核心),來深刻了解下Java的異步編程。bash
萬事從示例開始,咱們先經過示例Demo有一個直觀的印象,再深刻去了解概念與原理。微信
Demo: 多線程
運行結果:併發
任務1異步執行:0
任務2異步執行:0
任務2異步執行:1
...
任務2異步執行:45
同步代碼
任務2異步執行:24
...
任務1異步執行:199
任務1:執行完成
...
任務2異步執行:199
任務2:執行完成
複製代碼
倘若你屢次執行這個程序,會發現結果大大的不同,由於兩個任務和同步代碼是異步由多條線程執行的,打印的結果固然是隨機的。框架
回顧這個Demo作了什麼,異步
其中第二點是異步執行兩個任務,這兩個任務和主線程分別是用了三個線程併發執行的,第三點是在主線程中同步等待兩個任務的結果。異步編程
很容易看出來,異步編程的好處就在於可讓不相干的任務異步執行,不阻塞主線程。如果主線程須要異步執行的結果,此時再去等待結果會更加高效,提升程序的執行效率。源碼分析
下面來看看整個流程的實現原理。
通常在實際項目中,都會有配置有本身的線程池,建議你們在用異步編程時,配置一個專用的線程池,作好線程隔離,避免異步線程影響到其餘模塊的工做。Demo中爲了方便,直接調用Exectors的方法生成一個臨時的線程池,平常不建議使用。
咱們從這個ExecutorService.submit()
方法入手,看看總體實現。
ExecutorService.submit()
定義一個接口。這個接口接收一個Callable參數(執行的任務),返回一個Future(計算結果)。
Callable
,至關於一個須要執行的任務。它不接收任何參數,能夠返回結果,能夠拋出異常。相相似的還有Runnable
,它也是不接收,不一樣點在於它不返回結果,也不拋異常,異常須要在任務內部處理。總結來講Callable
更像一個方法的調用,Runnable
則是一個不須要理會結果的調用。在JDK 8之後,它們均可以經過Lamda表達式寫法去替代內部類的寫法(詳見Demo)。
Future
,一個異步計算的結果。調用get()
方法能夠獲得對應的計算結果,若是調用時沒有異步計算完,會阻塞等待計算的結果。同時它還提供方法能夠嘗試取消任務的執行。
看回ExecutorService.submit()
的實現,代碼在實現類AbstractExecutorService
中。
Callable
參數,實現類中還新增了接收
Runnable
參數的。
若是看過以前寫的《你真的懂ThreadPoolExecutor線程池技術嗎?看了源碼你會有全新的認識》,應該瞭解ThreadPoolExecutor
執行任務是能夠調用execute()
方法的。而這裏面submit()
方法則是爲Callable/Runnable
加多一層FutureTask
,從而 使執行結果有一個存放的地方,同時也添加一個能夠取消的功能。本來的execute()
只能執行任務,不會返回結果的,具體實現原理能夠看看以前的文章分析。
FutureTask
是RunnableFuture
的實現。而RunnableFuture
是繼承Future
和Runnable
接口的,定義run()
接口。
FutureTask
有
run()
接口,因此能夠直接用一個
Callable/Runnable
建立一個
FutureTask
單獨執行。但這樣並無異步的效果,由於沒有啓用新的線程去跑,而是在原來的線程阻塞執行的。
到這裏咱們清楚知道了,submit()
方法重點是利用Callable/Runnable
建立一個FutureTask
,而後多線程執行run()
方法,達到異步處理而且獲得結果的效果。而FutureTask
的重點則是run()
方法如何持有保存計算的結果。
futureTask
對象的
state
狀態,若是不是NEW的話,證實已經開始運行過了,則退出執行。同時
futureTask
對象經過CAS,把當前線程賦值給變量
runner
(是Thread類型,說明對象使用哪一個線程執行的),若是CAS失敗則退出。
外層try{}
代碼塊中,對callable
判空和state
狀態必須是NEW。內層try{}
代碼真正調用callable
,開始執行任務。若執行成功,則把ran
變量設爲true,保存結果在result
變量中,證實已跑成功過了;若拋異常了,則設爲false,result
爲空,而且調用setException()
保存異常。最後若是ran
爲true的話,則調用set()
保存result
結果。
看下setException()
和set()
的實現。
outcome
變量道中,但
setException()
保存的結果類型固定是
Throwable
。另一個不一樣在於最終
state
狀態,一個是EXCEPTION,一個是NORMAL。
這兩個方法最後都調用了finishCompletion()
。這個方法主要是配合線程池喚醒下一個任務。
從上面run()
方法得知,最後執行的結果放在了outcome
變量中。那最終怎麼從其中取出結果來,咱們來看看get()
方法。
get()
方法分兩步。第一步,先判斷狀態,若是計算爲完成,則須要阻塞地等待完成。第二步,若是完成了,則調用
report()
方法獲取結果並返回。
先看看awaitDone()
阻塞等待完成。該方法能夠選用超時功能。
state
值,若是state
大於COMPLETING
,證實計算已是終態了,此時返回終態變量。state
等於COMPLETING
,證實已經開始計算,而且還在計算中。此時爲了不過多的CPU時間放在這個for循環的自旋上,程序執行Thread.yield()
,把線程從運行態降爲就緒態,讓出CPU時間。state
爲NEW
,還沒開始執行。那麼程序在當前循環如今會新增一個WaitNode
,在下一個循環裏面調用LockSupport.park()
把當前線程阻塞。當run()
方法結束的時候,會再次喚醒此線程,避免自旋消耗CPU時間。第二步的report()
方法比較簡單。
NORMAL
,正常結束的話,則把outcome
變量返回;EXCEPTION
,則把outcome
看成異常拋出(以前setException()
保存的類型就是Throwable
)。從而整個get()
會有一個異常拋出。至此咱們已經比較完整地瞭解Executor+Future的框架原理了,而FutureTask則是該框架的主要實現。下面總結下要點
Executor.sumbit()
方法異步執行一個任務,而且返回一個Future結果。submit()
的原理是利用Callable
建立一個FutureTask
對象,而後執行對象的run()
方法,把結果保存在outcome
中。get()
獲取outcome
時,若是任務未完成,會阻塞線程,等待執行完畢。outcome
中,調用get()
獲取結果或拋出異常。更多技術文章、精彩乾貨,請關注
博客:zackku.com
微信公衆號:Zack說碼