SpringBoot的異步調用介紹

參考博客: http://www.javashuo.com/article/p-rsfnveoe-ce.htmlhtml

http://www.javashuo.com/article/p-ridlrord-hh.htmljava

何爲異步調用

說異步調用前,咱們說說它對應的同步調用。一般開發過程當中,通常上咱們都是同步調用,即:程序按定義的順序依次執行的過程,每一行代碼執行過程必須等待上一行代碼執行完畢後才執行。而異步調用指:程序在執行時,無需等待執行的返回值可繼續執行後面的代碼。顯而易見,同步有依賴相關性,而異步沒有,因此異步可併發執行,可提升執行效率,在相同的時間作更多的事情。web

題外話:處理異步、同步外,還有一個叫回調。其主要是解決異步方法執行結果的處理方法,好比在但願異步調用結束時返回執行結果,這個時候就能夠考慮使用回調機制。spring

先自定義一個線程池, 這裏採用java的代碼來配置, 以下面的ThreadPoolConfig.java內容:apache

import java.util.concurrent.ThreadPoolExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 @Configuration public class ThreadPoolConfig { @Bean(name = "asyncPoolTaskExecutor") // 這裏bean的name值在下面要用到 public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(20); taskExecutor.setMaxPoolSize(200); taskExecutor.setQueueCapacity(25); taskExecutor.setKeepAliveSeconds(200); taskExecutor.setThreadNamePrefix("cmnd-"); //線程池對拒絕任務(無線程可用)的處理策略,目前只支持AbortPolicy/CallerRunsPolicy;默認爲後者 taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //調度器shutdown被調用時等待當前被調度的任務完成 taskExecutor.setWaitForTasksToCompleteOnShutdown(true); //等待時長 taskExecutor.setAwaitTerminationSeconds(60); taskExecutor.initialize(); return taskExecutor; } }

這裏簡單說明下,關於ThreadPoolTaskExecutor參數說明:api

corePoolSize:線程池維護線程的最少數量
keepAliveSeconds:容許的空閒時間,當超過了核心線程出以外的線程在空閒時間到達以後會被銷燬
maxPoolSize:線程池維護線程的最大數量,只有在緩衝隊列滿了以後纔會申請超過核心線程數的線程
queueCapacity:緩存隊列
rejectedExecutionHandler:線程池對拒絕任務(無線程可用)的處理策略。這裏採用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute 方法的調用線程中運行被拒絕的任務;若是執行程序已關閉,則會丟棄該任務。還有一個是AbortPolicy策略:處理程序遭到拒絕將拋出運行時RejectedExecutionException。
而在一些場景下,若須要在關閉線程池時等待當前調度任務完成後纔開始關閉,能夠經過簡單的配置,進行優雅的停機策略配置。關鍵就是經過setWaitForTasksToCompleteOnShutdown(true)和setAwaitTerminationSeconds方法。瀏覽器

setWaitForTasksToCompleteOnShutdown:代表等待全部線程執行完,默認爲false。
setAwaitTerminationSeconds:等待的時間,由於不能無限的等待下去緩存


再給出AsyncService.java內容併發

import java.util.concurrent.Future;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

@Service(value = "asyncService")
public class AsyncService {

    private static final Logger LOG = LogManager.getLogger(AsyncService.class);
   
    // 我這裏寫的異步任務是必須有返回值的, 使用AsyncResult來返回
    @Async(value= "asyncPoolTaskExecutor") // 這裏的value必須寫成上面自定義的線程池name, 要否則會用默認的線程池來執行下面的代碼
    public Future<String> testAsync(int i) {
        try {                  
            Thread.sleep(1000);            
        } catch (InterruptedException e) {
            LOG.error(e.getMessage(), e);
            Thread.currentThread().interrupt();
        } 
        return new AsyncResult<>(String.format("testAsyncTask->{%s}", i));
    }
    
}

最後給出AsyncTaskController.java內容:app

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.xum.cmnd.service.AsyncService;

@EnableAsync
@Controller(value = "asyncTaskController")
@RequestMapping(value = "/async")
public class AsyncTaskController {

    private static final Logger LOG = LogManager.getLogger(AsyncTaskController.class);
    
    @Autowired
    private AsyncService asyncService;
    
    @PostMapping(value = "/test")
    public void testAsync() throws InterruptedException, ExecutionException {
        List<Future<String>> futures = new ArrayList<>();
        for(int i = 1; i <= 10; i++) {
            Future<String> future = asyncService.testAsync(i);
            futures.add(future);
        }
        for (Future<String> future : futures) {
            String result = future.get();
            LOG.info("result:" + result);
        }
    }
    
}

啓動項目, 而後用postman或則在瀏覽器中輸入http://localhost:8080/async/test這條api, 看eclipse的console中打印的log, 以下

...controller.AsyncTaskController][37]:result:testAsyncTask->{1}
...controller.AsyncTaskController][37]:result:testAsyncTask->{2}
...controller.AsyncTaskController][37]:result:testAsyncTask->{3}
...controller.AsyncTaskController][37]:result:testAsyncTask->{4}
...controller.AsyncTaskController][37]:result:testAsyncTask->{5}
...controller.AsyncTaskController][37]:result:testAsyncTask->{6}
...controller.AsyncTaskController][37]:result:testAsyncTask->{7}
...controller.AsyncTaskController][37]:result:testAsyncTask->{8}
...controller.AsyncTaskController][37]:result:testAsyncTask->{9}
...controller.AsyncTaskController][37]:result:testAsyncTask->{10}

注意:

1. 必定要批量讀取結果, 不然不能達到異步的效果!!!

二、異步方法和調用類不要在同一個類中!!!

三、註解掃描時,要注意過濾,避免重複實例化,由於存在覆蓋問題,@Async就失效了!!!

相關文章
相關標籤/搜索