一些業務場景我們需要使用多執行緒非同步執行任務,加快任務執行速度。
之前有寫過一篇文章叫做: 非同步程式設計利器:CompletableFuture
在實際工作中也更加推薦使用CompletableFuture
,因為它實現非同步方式更加優雅,而且功能更加強大!
既然SpringBoot能通過 @Async 也實現非同步執行任務,那麼這篇文章就來總結下如何使用 @Async
實現非同步執行任務。
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Componet
public class Test{
}
除了@Componet,也可以是@Controller、@RestController、@Service、@Configuration等註解,加入到Ioc容器裡。
@Service
public class Test{
@Async
public void a() {
}
}
如果你明明是按照上面的步驟來的,但是發現@Async註解還是不起作用,這裡還有兩點注意,因為@Async
是基於Aop思想實現的,所有下面兩種情況也會失效。
@Async
public static void a() {
}
當非同步方法和呼叫方法在同一個類中時,是沒辦法通過Ioc裡的bean來執行非同步方法的,從而變成同步方法。
如下:
@Component
public class Task {
/**
* 調非同步方法和非同步方法在同一個類 @Async執行失敗
*/
public void dotask() {
this.taskOne();
this.taskTwo();
}
@Async
public void taskOne() {
//執行任務1
}
@Async
public void taskTwo() {
//執行任務2
}
}
首先我們來看同步方法
@Component
@Slf4j
public class DemoTask {
public void taskOne() throws Exception {
log.info("===執行任務1===");
long start = System.currentTimeMillis();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("任務1執行結束,總耗時={} 毫秒", end - start);
}
public void taskTwo() throws Exception {
log.info("===執行任務2===");
long start = System.currentTimeMillis();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("任務2執行結束,總耗時={} 毫秒", end - start);
}
public void taskThere() throws Exception {
log.info("===執行任務3===");
long start = System.currentTimeMillis();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("任務3執行結束,總耗時={} 毫秒", end - start);
}
}
執行方法
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoTaskTest {
@Autowired
private DemoTask demoTask;
@Test
public void runDemo() throws Exception {
long start = System.currentTimeMillis();
demoTask.taskOne();
demoTask.taskTwo();
demoTask.taskThere();
long end = System.currentTimeMillis();
log.info("總任務執行結束,總耗時={} 毫秒", end - start);
}
}
輸出紀錄檔
===執行任務1===
任務1執行結束,總耗時=204 毫秒
===執行任務2===
任務2執行結束,總耗時=203 毫秒
===執行任務3===
任務3執行結束,總耗時=201 毫秒
總任務執行結束,總耗時=613 毫秒
非同步方法
@Component
@Slf4j
public class AsyncTask {
@Async
public void taskOne() throws Exception {
//執行內容同上,省略
}
@Async
public void taskTwo() throws Exception {
//執行內容同上,省略
}
@Async
public void taskThere() throws Exception {
//執行內容同上,省略
}
}
呼叫方法
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@EnableAsync
@SpringBootTest
public class AsyncTest {
@Autowired
private AsyncTask asyncTask;
@Test
public void runAsync() throws Exception {
long start = System.currentTimeMillis();
asyncTask.taskOne();
asyncTask.taskTwo();
asyncTask.taskThere();
Thread.sleep(200);
long end = System.currentTimeMillis();
log.info("總任務執行結束,總耗時={} 毫秒", end - start);
}
}
檢視紀錄檔
===執行任務1===
===執行任務3===
===執行任務2===
總任務執行結束,總耗時=206 毫秒
任務1執行結束,總耗時=200 毫秒
任務2執行結束,總耗時=201 毫秒
任務3執行結束,總耗時=201 毫秒
通過紀錄檔可以看出已經是已經實現非同步處理任務了,而且非同步任務哪個先執行是不確定的。
如果我想非同步執行,同時想獲取所有非同步執行的結果,那麼這個時候就需要採用Future。
非同步方法
@Component
@Slf4j
public class FutureTask {
@Async
public Future<String> taskOne() throws Exception {
//執行內容同上,省略
return new AsyncResult<>("1完成");
}
@Async
public Future<String> taskTwo() throws Exception {
//執行內容同上,省略
return new AsyncResult<>("2完成");
}
@Async
public Future<String> taskThere() throws Exception {
//執行內容同上,省略
return new AsyncResult<>("執行任務3完成");
}
}
呼叫方法
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAsync
public class FutureTaskTest {
@Autowired
private FutureTask futureTask;
@Test
public void runAsync() throws Exception {
long start = System.currentTimeMillis();
Future<String> taskOne = futureTask.taskOne();
Future<String> taskTwo = futureTask.taskTwo();
Future<String> taskThere = futureTask.taskThere();
while (true) {
if (taskOne.isDone() && taskTwo.isDone() && taskThere.isDone()) {
log.info("任務1返回結果={},任務2返回結果={},任務3返回結果={},", taskOne.get(), taskTwo.get(), taskThere.get());
break;
}
}
long end = System.currentTimeMillis();
log.info("總任務執行結束,總耗時={} 毫秒", end - start);
}
}
檢視紀錄檔
===執行任務2===
===執行任務3===
===執行任務1===
任務1執行結束,總耗時=201 毫秒
任務3執行結束,總耗時=201 毫秒
任務2執行結束,總耗時=201 毫秒
任務1返回結果=1完成,任務2返回結果=2完成,任務3返回結果=執行任務3完成,
總任務執行結束,總耗時=223 毫秒
從紀錄檔可以看出 非同步任務的執行結果都有獲取。
如果不自定義非同步方法的執行緒池預設使用SimpleAsyncTaskExecutor執行緒池。
SimpleAsyncTaskExecutor
:不是真的執行緒池,這個類不重用執行緒,每次呼叫都會建立一個新的執行緒。並行大的時候會產生嚴重的效能問題。
Spring也更加推薦我們開發者使用ThreadPoolTaskExecutor類來建立執行緒池。
自定義執行緒池
@Configuration
public class ExecutorAsyncConfig {
@Bean(name = "newAsyncExecutor")
public Executor newAsync() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//設定核心執行緒數
taskExecutor.setCorePoolSize(10);
// 執行緒池維護執行緒的最大數量,只有在緩衝佇列滿了以後才會申請超過核心執行緒數的執行緒
taskExecutor.setMaxPoolSize(100);
//快取佇列
taskExecutor.setQueueCapacity(50);
//允許的空閒時間,當超過了核心執行緒數之外的執行緒在空閒時間到達之後會被銷燬
taskExecutor.setKeepAliveSeconds(200);
//非同步方法內部執行緒名稱
taskExecutor.setThreadNamePrefix("my-xiaoxiao-AsyncExecutor-");
//拒絕策略
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
範例程式碼
任務1和任務2設定走我們自定義的執行緒池,任務3還是走預設執行緒池。
@Component
@Slf4j
public class FutureExecutorTask {
@Async("newAsyncExecutor")
public Future<String> taskOne() throws Exception {
log.info("任務1執行緒名稱 = {}", Thread.currentThread().getName());
return new AsyncResult<>("1完成");
}
@Async("newAsyncExecutor")
public Future<String> taskTwo() throws Exception {
log.info("任務2執行緒名稱 = {}", Thread.currentThread().getName());
return new AsyncResult<>("2完成");
}
@Async
public Future<String> taskThere() throws Exception {
log.info("任務3執行緒名稱 = {}", Thread.currentThread().getName());
return new AsyncResult<>("執行任務3完成");
}
}
調研方法和上面一樣,我們再來看下紀錄檔
任務2執行緒名稱 = my-xiaoxiao-AsyncExecutor-2
任務1執行緒名稱 = my-xiaoxiao-AsyncExecutor-1
任務3執行緒名稱 = SimpleAsyncTaskExecutor-1
總任務執行結束,總耗時=15 毫秒
通過紀錄檔我們可以看出 任務1和任務2走的是我們自定義的執行緒池,任務3還是走預設執行緒池。
推薦這種方式來實現非同步,它不需要在啟動類上加@EnableAsync
註解,也不需要在方法上加@Async
註解,它實現更加優雅,而且CompletableFuture功能更加強大。
具體可以看下之前寫的文章:非同步程式設計利器:CompletableFuture
看如何使用
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class CompletableTest {
@Autowired
private DemoTask dmoTask;
@Test
public void testCompletableThenRunAsync() throws Exception {
long startTime = System.currentTimeMillis();
CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> {
//任務1
dmoTask.taskOne();
});
CompletableFuture<Void> cp2 = CompletableFuture.runAsync(() -> {
//任務2
dmoTask.taskTwo();
});
CompletableFuture<Void> cp3 = CompletableFuture.runAsync(() -> {
//任務3
dmoTask.taskThere();
});
cp1.get();
cp2.get();
cp3.get();
//模擬主程式耗時時間
System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
}
}
檢視紀錄檔
===執行任務1===
===執行任務2===
===執行任務3===
任務3執行結束,總耗時=204 毫秒
任務2執行結束,總耗時=203 毫秒
任務1執行結束,總耗時=204 毫秒
總共用時226ms
從紀錄檔可以看出,通過CompletableFuture同樣可以實現非同步執行任務!
宣告: 公眾號如需轉載該篇文章,發表文章的頭部一定要 告知是轉至公眾號: 後端元宇宙。同時也可以問本人要markdown原稿和原圖片。其它情況一律禁止轉載!