Spring Boot使用@Async實現異步調用

異步調用對應的是同步調用,同步調用能夠理解爲按照定義的順序依次執行,有序性;異步調用在執行的時候不須要等待上一個指令調用結束就能夠繼續執行。css

咱們將在建立一個 Spring Boot 工程來講明。具體工程能夠參考github代碼 github.com/UniqueDong/… async模塊java

pom 依賴以下:git

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.2</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
複製代碼

啓動類以下:github

@SpringBootApplication
public class AsyncApplication {

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

}
複製代碼

定義線程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/** * 異步線程池 */
@Configuration
@EnableAsync
public class AsyncExecutorConfig {

    /** * Set the ThreadPoolExecutor's core pool size. */
    private int corePoolSize = 8;
    /** * Set the ThreadPoolExecutor's maximum pool size. */
    private int maxPoolSize = 16;
    /** * Set the capacity for the ThreadPoolExecutor's BlockingQueue. */
    private int queueCapacity = 200;

    private String threadNamePrefix = "AsyncExecutor-";

    @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix(threadNamePrefix);

        // rejection-policy:當pool已經達到max size的時候,如何處理新任務 
        // CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行 
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}  

複製代碼

代碼中咱們經過 ThreadPoolTaskExecutor 建立了一個線程池。參數含義以下所示:spring

  • corePoolSize:線程池建立的核心線程數
  • maxPoolSize:線程池最大線程池數量,當任務數超過corePoolSize以及緩衝隊列也滿了之後纔會申請的線程數量。
  • setKeepAliveSeconds: 容許線程空閒時間60秒,當maxPoolSize的線程在空閒時間到達的時候銷燬。
  • ThreadNamePrefix:線程的前綴任務名字。
  • RejectedExecutionHandler:當線程池沒有處理能力的時候,該策略會直接在 execute 方法的調用線程中運行被拒絕的任務;若是執行程序已關閉,則會丟棄該任務

使用實戰

@Slf4j
@Service
public class OrderService {
    public static Random random = new Random();


    @Autowired
    private AsyncTask asyncTask;

    public void doShop() {
        try {
            createOrder();
            // 調用有結果返回的異步任務
            Future<String> pay = asyncTask.pay();
            if (pay.isDone()) {
                try {
                    String result = pay.get();
                    log.info("異步任務返回結果{}", result);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                asyncTask.vip();
                asyncTask.sendSms();
            }
            otherJob();
        } catch (InterruptedException e) {
            log.error("異常", e);
        }
    }

    public void createOrder() {
        log.info("開始作任務1:下單成功");
    }

    /** * 錯誤使用,不會異步執行:調用方與被調方不能在同一個類。主要是使用了動態代理,同一個類的時候直接調用,不是經過生成的動態代理類調用 */
    @Async("taskExecutor")
    public void otherJob() {
        log.info("開始作任務4:物流");
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(random.nextInt(10000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        log.info("完成任務4,耗時:" + (end - start) + "毫秒");
    }

}
複製代碼

異步任務服務類springboot

mport lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

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

@Component
@Slf4j
public class AsyncTask {
    public static Random random = new Random();


    @Async("taskExecutor")
    public void sendSms() throws InterruptedException {
        log.info("開始作任務2:發送短信");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務1,耗時:" + (end - start) + "毫秒");
    }

    @Async("taskExecutor")
    public Future<String> pay() throws InterruptedException {
        log.info("開始作異步返回結果任務2:支付");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任務2,耗時:" + (end - start) + "毫秒");
        return new AsyncResult<>("會員服務完成");
    }

    /** * 返回結果的異步調用 * @throws InterruptedException */
    @Async("taskExecutor")
    public void vip() throws InterruptedException {
        log.info("開始作任務5:會員");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("開始作異步返回結果任務5,耗時:" + (end - start) + "毫秒");
    }
}
複製代碼

單元測試

@RunWith(SpringRunner.class)
@SpringBootTest(classes = AsyncApplication.class)
public class AsyncApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    public void testAsync() {
        orderService.doShop();
        try {
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
複製代碼

結果展現dom

2019-05-16 20:25:06.577 [INFO ] [main] - zero.springboot.study.async.service.OrderService-52 開始作任務1:下單成功 
2019-05-16 20:25:06.586 [INFO ] [main] - zero.springboot.study.async.service.OrderService-60 開始作任務4:物流 
2019-05-16 20:25:06.599 [INFO ] [AsyncExecutor-1] - zero.springboot.study.async.service.AsyncTask-38 開始作異步返回結果任務2:支付 
2019-05-16 20:25:13.382 [INFO ] [AsyncExecutor-1] - zero.springboot.study.async.service.AsyncTask-42 完成任務2,耗時:6783毫秒 
2019-05-16 20:25:14.771 [INFO ] [main] - zero.springboot.study.async.service.OrderService-68 完成任務4,耗時:8184毫秒 
複製代碼

能夠看到有的線程的名字就是咱們線程池定義的前綴,說明使用了線程池異步執行。其中咱們示範了一個錯誤的使用案例 otherJob(),並無異步執行。異步

緣由:async

spring 在掃描bean的時候會掃描方法上是否包含@Async註解,若是包含,spring會爲這個bean動態地生成一個子類(即代理類,proxy),代理類是繼承原來那個bean的。此時,當這個有註解的方法被調用的時候,其實是由代理類來調用的,代理類在調用時增長異步做用。然而,若是這個有註解的方法是被同一個類中的其餘方法調用的,那麼該方法的調用並無經過代理類,而是直接經過原來的那個 bean 也就是 this. method,因此就沒有增長異步做用,咱們看到的現象就是@Async註解無效。spring-boot

關注公衆號 JavaStorm

相關文章
相關標籤/搜索