什麼是「異步調用」?併發
「異步調用」對應的是「同步調用」,同步調用指程序按照定義順序依次執行,每一行程序都必須等待上一行程序執行完成以後才能執行;異步調用指程序在順序執行時,不等待異步調用的語句返回結果就執行後面的程序。dom
下面經過一個簡單示例來直觀的理解什麼是同步調用:異步
1函數 2單元測試 3測試 4spa 5.net 6code 7對象 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@Component public class Task { public static Random random =new Random(); public void doTaskOne() throws Exception { System.out.println("開始作任務一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務一,耗時:" + (end - start) + "毫秒"); } public void doTaskTwo() throws Exception { System.out.println("開始作任務二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務二,耗時:" + (end - start) + "毫秒"); } public void doTaskThree() throws Exception { System.out.println("開始作任務三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務三,耗時:" + (end - start) + "毫秒"); } } |
doTaskOne
、doTaskTwo
、doTaskThree
三個函數。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = Application.class) public class ApplicationTests { @Autowired private Task task; public void test() throws Exception { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); } } |
1 2 3 4 5 6 |
開始作任務一 完成任務一,耗時:4256毫秒 開始作任務二 完成任務二,耗時:4957毫秒 開始作任務三 完成任務三,耗時:7173毫秒 |
任務1、任務2、任務三順序的執行完了,換言之doTaskOne
、doTaskTwo
、doTaskThree
三個函數順序的執行完成。
上述的同步調用雖然順利的執行完了三個任務,可是能夠看到執行時間比較長,若這三個任務自己之間不存在依賴關係,能夠併發執行的話,同步調用在執行效率方面就比較差,能夠考慮經過異步調用的方式來併發執行。
在Spring Boot中,咱們只須要經過使用@Async
註解就能簡單的將原來的同步函數變爲異步函數,Task類改在爲以下模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Component public class Task { @Async public void doTaskOne() throws Exception { // 同上內容,省略 } @Async public void doTaskTwo() throws Exception { // 同上內容,省略 } @Async public void doTaskThree() throws Exception { // 同上內容,省略 } } |
爲了讓@Async註解可以生效,還須要在Spring Boot的主程序中配置@EnableAsync,以下所示:
1 2 3 4 5 6 7 8 9 |
@SpringBootApplication @EnableAsync public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } |
此時能夠反覆執行單元測試,您可能會遇到各類不一樣的結果,好比:
緣由是目前doTaskOne
、doTaskTwo
、doTaskThree
三個函數的時候已是異步執行了。主程序在異步調用以後,主程序並不會理會這三個函數是否執行完成了,因爲沒有其餘須要執行的內容,因此程序就自動結束了,致使了不完整或是沒有輸出任務相關內容的狀況。
注: @Async所修飾的函數不要定義爲static類型,這樣異步調用不會生效
爲了讓doTaskOne
、doTaskTwo
、doTaskThree
能正常結束,假設咱們須要統計一下三個任務併發執行共耗時多少,這就須要等到上述三個函數都完成調動以後記錄時間,並計算結果。
那麼咱們如何判斷上述三個異步調用是否已經執行完成呢?咱們須要使用Future<T>
來返回異步調用的結果,就像以下方式改造doTaskOne
函數:
1 2 3 4 5 6 7 8 9 |
@Async public Future<String> doTaskOne() throws Exception { System.out.println("開始作任務一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務一,耗時:" + (end - start) + "毫秒"); return new AsyncResult<>("任務一完成"); } |
按照如上方式改造一下其餘兩個異步函數以後,下面咱們改造一下測試用例,讓測試在等待完成三個異步調用以後來作一些其餘事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Test public void test() throws Exception { long start = System.currentTimeMillis(); Future<String> task1 = task.doTaskOne(); Future<String> task2 = task.doTaskTwo(); Future<String> task3 = task.doTaskThree(); while(true) { if(task1.isDone() && task2.isDone() && task3.isDone()) { // 三個任務都調用完成,退出循環等待 break; } Thread.sleep(1000); } long end = System.currentTimeMillis(); System.out.println("任務所有完成,總耗時:" + (end - start) + "毫秒"); } |
看看咱們作了哪些改變:
Future<String>
類型的結果對象Future<String>
對象來判斷三個異步函數是否都結束了。若都結束,就結束循環;若沒有都結束,就等1秒後再判斷。執行一下上述的單元測試,能夠看到以下結果:
1 2 3 4 5 6 7 |
開始作任務一 開始作任務二 開始作任務三 完成任務三,耗時:37毫秒 完成任務二,耗時:3661毫秒 完成任務一,耗時:7149毫秒 任務所有完成,總耗時:8025毫秒 |
能夠看到,經過異步調用,讓任務1、2、三併發執行,有效的減小了程序的總運行時間。