1. 程式人生 > >springboot乾貨——(十六)使用@Async實現非同步呼叫

springboot乾貨——(十六)使用@Async實現非同步呼叫

非同步呼叫針對的是同步呼叫,一般在程式碼中我們使用同步呼叫相對較多,即請求程式碼立即返回結果或者說執行程式碼,非同步呼叫則是指請求之後不會裡面返回結果或者是呼叫程式碼。

接下來我們用例項來看下什麼是同步呼叫:

新建一個springboot專案後建立對應的task類:

package com.gwd.task;

import java.util.Random;

import org.springframework.stereotype.Component;

/** 
* @FileName Task.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年2月24日 下午2:48:47 
* 修改歷史:
* 時間           作者          版本        描述
*====================================================  
*
*/
@Component
public class Task {
	public static Random random =new Random();
	
	public void taskOne() 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 taskTwo() 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 taskThree() throws Exception {
		System.out.println("開始執行任務三");
		long start=System.currentTimeMillis();
		Thread.sleep(random.nextInt(10000));
		long end=System.currentTimeMillis();
		System.out.println("任務三執行時間:"+(end- start));
	}
}

測試類如下:
package com.gwd;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.gwd.task.Task;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAsyncApplicationTests {
	@Autowired
	private Task task;
	@Test
	public void test() throws Exception {
		task.taskOne();
		task.taskTwo();
		task.taskThree();
	}
}

測試結果如下:
開始執行任務一
任務一執行時間:9019
開始執行任務二
任務二執行時間:7612
開始執行任務三
任務三執行時間:365
從結果上我們不難看出這三個是按順序執行的。


接下來我們來看看非同步呼叫:

1.為了讓@Async註解能夠生效,還需要在Spring Boot的主程式中配置@EnableAsync,如下所示:

package com.gwd;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootAsyncApplication.class, args);
	}
}

2.在Spring Boot中,我們只需要通過使用@Async註解就能簡單的將原來的同步函式變為非同步函式,Task類改在為如下模式:
package com.gwd.task;

import java.util.Random;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/** 
* @FileName Task.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年2月24日 下午2:48:47 
* 修改歷史:
* 時間           作者          版本        描述
*====================================================  
*
*/
@Component
public class Task {
	public static Random random =new Random();
	
	@Async
	public void taskOne() throws Exception {
		System.out.println("開始執行任務一");
		long start=System.currentTimeMillis();
		Thread.sleep(random.nextInt(10000));
		long end=System.currentTimeMillis();
		System.out.println("任務一執行時間:"+(end- start));
	}
	
	@Async
	public void taskTwo() throws Exception {
		System.out.println("開始執行任務二");
		long start=System.currentTimeMillis();
		Thread.sleep(random.nextInt(10000));
		long end=System.currentTimeMillis();
		System.out.println("任務二執行時間:"+(end- start));
	}
	@Async
	public void taskThree() throws Exception {
		System.out.println("開始執行任務三");
		long start=System.currentTimeMillis();
		Thread.sleep(random.nextInt(10000));
		long end=System.currentTimeMillis();
		System.out.println("任務三執行時間:"+(end- start));
	}
}
此時可以反覆執行單元測試,您可能會遇到各種不同的結果,比如:
沒有任何任務相關的輸出
有部分任務相關的輸出
亂序的任務相關的輸出

原因是目前taskOne、taskTwo、taskThree三個函式的時候已經是非同步執行了。主程式在非同步呼叫之後,主程式並不會理會這三個函式是否執行完成了,由於沒有其他需要執行的內容,所以程式就自動結束了,導致了不完整或是沒有輸出任務相關內容的情況。

注: @Async所修飾的函式不要定義為static型別,這樣非同步呼叫不會生效

非同步回撥

為了讓taskOne、taskTwo、taskThree能正常結束,假設我們需要統計一下三個任務併發執行共耗時多少,這就需要等到上述三個函式都完成調動之後記錄時間,並計算結果。

那麼我們如何判斷上述三個非同步呼叫是否已經執行完成呢?我們需要使用Future<T>來返回非同步呼叫的結果,就像如下方式改造task類:

package com.gwd.task;

import java.util.Random;
import java.util.concurrent.Future;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

/** 
* @FileName Task.java
* @Description:TODO
* @author JackHisen(gu.weidong)
* @version V1.0
* @createtime 2018年2月24日 下午2:48:47 
* 修改歷史:
* 時間           作者          版本        描述
*====================================================  
*
*/
@Component
public class Task {
	public static Random random =new Random();
	
	@Async
	public Future<String> taskOne() 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<>("任務一完成");
	}
	
	@Async
	public Future<String> taskTwo() 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<>("任務二完成");
	}
	@Async
	public Future<String> taskThree() 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<>("任務三完成");
	}
}

按照如上方式改造一下其他兩個非同步函式之後,下面我們改造一下測試用例,讓測試在等待完成三個非同步呼叫之後來做一些其他事情。

package com.gwd;

import java.util.concurrent.Future;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.gwd.task.Task;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAsyncApplicationTests {
	@Autowired
	private Task task;
	@Test
	public void test() throws Exception {
		long start = System.currentTimeMillis();

		Future<String> task1 = task.taskOne();
		Future<String> task2 = task.taskTwo();
		Future<String> task3 = task.taskThree();

		while(true) {
			if(task1.isDone() && task2.isDone() && task3.isDone()) {
				// 三個任務都呼叫完成,退出迴圈等待
				break;
			}
			Thread.sleep(1000);
		}
		long end = System.currentTimeMillis();

		System.out.println("任務全部完成,總耗時:" + (end - start) + "毫秒");
	}
}
看看我們做了哪些改變:

1.在測試用例一開始記錄開始時間
2.在呼叫三個非同步函式的時候,返回Future<String>型別的結果物件
3.在呼叫完三個非同步函式之後,開啟一個迴圈,根據返回的Future<String>物件來判斷三個非同步函式是否都結束了。若都結束,就結束迴圈;若沒有都結束,就等1秒後再判斷。
4.跳出迴圈之後,根據結束時間 - 開始時間,計算出三個任務併發執行的總耗時。
執行一下上述的單元測試,可以看到如下結果:
開始執行任務二
開始做任務一
開始執行任務三
完成任務二,耗時:5700毫秒
完成任務三,耗時:7232毫秒
完成任務一,耗時:7325毫秒
任務全部完成,總耗時:8006毫秒

可以看到,通過非同步呼叫,讓任務一、二、三併發執行,有效的減少了程式的總執行時間。