Spring Boot教程(十六)使用@Async實現異步調用

同步調用

下面經過一個簡單示例來直觀的理解什麼是同步調用:html

  • 定義Task類,建立三個處理函數分別模擬三個執行任務的操做,操做消耗時間隨機取(10秒內)
    @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) + "毫秒");
        }
    
    }

     

  • 在單元測試用例中,注入Task對象,並在測試用例中執行doTaskOnedoTaskTwodoTaskThree三個函數。
    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = Application.class)
    public class ApplicationTests {
    
    	@Autowired
    	private Task task;
    
    	@Test
    	public void test() throws Exception {
    		task.doTaskOne();
    		task.doTaskTwo();
    		task.doTaskThree();
    	}
    
    }

     

  • 執行單元測試,能夠看到相似以下輸出:
    開始作任務一
    完成任務一,耗時:4256毫秒
    開始作任務二
    完成任務二,耗時:4957毫秒
    開始作任務三
    完成任務三,耗時:7173毫秒

    任務1、任務2、任務三順序的執行完了,換言之doTaskOnedoTaskTwodoTaskThree三個函數順序的執行完成。併發

    異步調用

    上述的同步調用雖然順利的執行完了三個任務,可是能夠看到執行時間比較長,若這三個任務自己之間不存在依賴關係,能夠併發執行的話,同步調用在執行效率方面就比較差,能夠考慮經過異步調用的方式來併發執行。dom

    在Spring Boot中,咱們只須要經過使用@Async註解就能簡單的將原來的同步函數變爲異步函數,Task類改在爲以下模式:異步

    @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,以下所示:函數

    @SpringBootApplication
    @EnableAsync
    public class Application {
    
    	public static void main(String[] args) {
    		SpringApplication.run(Application.class, args);
    	}
    
    }

    此時能夠反覆執行單元測試,您可能會遇到各類不一樣的結果,好比:單元測試

  • 沒有任何任務相關的輸出
  • 有部分任務相關的輸出
  • 亂序的任務相關的輸出
  • 緣由是目前doTaskOnedoTaskTwodoTaskThree三個函數的時候已是異步執行了。主程序在異步調用以後,主程序並不會理會這三個函數是否執行完成了,因爲沒有其餘須要執行的內容,因此程序就自動結束了,致使了不完整或是沒有輸出任務相關內容的狀況。測試

    注: @Async所修飾的函數不要定義爲static類型,這樣異步調用不會生效spa

    異步回調

  • 爲了讓doTaskOnedoTaskTwodoTaskThree能正常結束,假設咱們須要統計一下三個任務併發執行共耗時多少,這就須要等到上述三個函數都完成調動以後記錄時間,並計算結果。.net

    那麼咱們如何判斷上述三個異步調用是否已經執行完成呢?咱們須要使用Future<T>來返回異步調用的結果,就像以下方式改造doTaskOne函數:code

    @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<>("任務一完成");
    }

    按照如上方式改造一下其餘兩個異步函數以後,下面咱們改造一下測試用例,讓測試在等待完成三個異步調用以後來作一些其餘事情。

    @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秒後再判斷。
  • 跳出循環以後,根據結束時間 - 開始時間,計算出三個任務併發執行的總耗時。
  • 執行一下上述的單元測試,能夠看到以下結果:
    開始作任務一
    開始作任務二
    開始作任務三
    完成任務三,耗時:37毫秒
    完成任務二,耗時:3661毫秒
    完成任務一,耗時:7149毫秒
    任務所有完成,總耗時:8025毫秒

    能夠看到,經過異步調用,讓任務1、2、三併發執行,有效的減小了程序的總運行時間。

  • 源碼來源

相關文章
相關標籤/搜索