SpringBoot系列——@Async優雅的異步調用

  前言

  衆所周知,java的代碼是同步順序執行,當咱們須要執行異步操做時咱們須要建立一個新線程去執行,以往咱們是這樣操做的:html

    /**
     * 任務類
     */
    class Task implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ":異步任務");
        }
    }
        //新建線程並執行任務類
        new Thread(new Task()).start();

   jdk1.8以後可使用Lambda 表達式java

        //新建線程並執行任務類
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":異步任務");
        }).start();

  固然,除了顯式的new Thread,咱們通常經過線程池獲取線程,這裏就再也不展開mysql

 

  Spring 3.0以後提供了一個@Async註解,使用@Async註解進行優雅的異步調用,咱們先看一下API對這個註解的定義:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/Async.htmlgit

 

  本文記錄在SpringBoot項目中使用@Async註解,實現優雅的異步調用github

 

   代碼與測試

  項目工程結構spring

  由於要測試事務,因此須要引入sql

        <!--添加springdata-jpa依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--添加MySQL驅動依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

 

  在啓動類開啓啓用異步調用,同時注入ApplicationRunner對象在啓動類進行調用測試api

package cn.huanzi.qch.springbootasync;

import cn.huanzi.qch.springbootasync.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

@Component
@EnableAsync//開啓異步調用
@SpringBootApplication
public class SpringbootAsyncApplication {

    @Autowired
    private TestService testService;

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

    /**
     * 啓動成功
     */
    @Bean
    public ApplicationRunner applicationRunner() {
        return applicationArguments -> {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");
            //無返回值
//            testService.asyncTask();

            //有返回值,但主線程不須要用到返回值
//            Future<String> future = testService.asyncTask("huanzi-qch");
            //有返回值,且主線程須要用到返回值
//            System.out.println(Thread.currentThread().getName() + ":返回值:" + testService.asyncTask("huanzi-qch").get());

            //事務測試,事務正常提交
//            testService.asyncTaskForTransaction(false);
            //事務測試,模擬異常事務回滾
//            testService.asyncTaskForTransaction(true);

            long endTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime));
        };
    }
}

  看一下咱們的測試業務類TestServicespringboot

package cn.huanzi.qch.springbootasync.service;

import java.util.concurrent.Future;

public interface TestService {
    /**
     * 異步調用,無返回值
     */
    void asyncTask();

    /**
     * 異步調用,有返回值
     */
    Future<String> asyncTask(String s);

    /**
     * 異步調用,無返回值,事務測試
     */
    void asyncTaskForTransaction(Boolean exFlag);
}
package cn.huanzi.qch.springbootasync.service;

import cn.huanzi.qch.springbootasync.pojo.TbUser;
import cn.huanzi.qch.springbootasync.repository.TbUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.Future;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TbUserRepository tbUserRepository;

    @Async
    @Override
    public void asyncTask() {
        long startTime = System.currentTimeMillis();
        try {
            //模擬耗時
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + ":void asyncTask(),耗時:" + (endTime - startTime));
    }

    @Async("asyncTaskExecutor")
    @Override
    public Future<String> asyncTask(String s) {
        long startTime = System.currentTimeMillis();
        try {
            //模擬耗時
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + ":Future<String> asyncTask(String s),耗時:" + (endTime - startTime));
        return AsyncResult.forValue(s);
    }

    @Async("asyncTaskExecutor")
    @Transactional
    @Override
    public void asyncTaskForTransaction(Boolean exFlag) {
        //新增一個用戶
        TbUser tbUser = new TbUser();
        tbUser.setUsername("huanzi-qch");
        tbUser.setPassword("123456");
        tbUserRepository.save(tbUser);

        if(exFlag){
            //模擬異常
            throw new RuntimeException("模擬異常");
        }
    }
}

 

  配置線程池

package cn.huanzi.qch.springbootasync.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 線程池的配置
 */
@Configuration
public class AsyncConfig {

    private static final int MAX_POOL_SIZE = 50;

    private static final int CORE_POOL_SIZE = 20;

    @Bean("asyncTaskExecutor")
    public AsyncTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
        asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        asyncTaskExecutor.setThreadNamePrefix("async-task-thread-pool-");
        asyncTaskExecutor.initialize();
        return asyncTaskExecutor;
    }
}

  配置好後,@Async會默認從線程池獲取線程,固然也能夠顯式的指定@Async("asyncTaskExecutor")app

 

  無返回值

    /**
     * 啓動成功
     */
    @Bean
    public ApplicationRunner applicationRunner() {
        return applicationArguments -> {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");
//無返回值 testService.asyncTask();
long endTime = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime)); }; }

 

 

  有返回值

  有返回值,但主線程不須要用到返回值

    /**
     * 啓動成功
     */
    @Bean
    public ApplicationRunner applicationRunner() {
        return applicationArguments -> {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");//有返回值,但主線程不須要用到返回值
            Future<String> future = testService.asyncTask("huanzi-qch");

            long endTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime));
        };
    }

 

  有返回值,且主線程須要用到返回值

    /**
     * 啓動成功
     */
    @Bean
    public ApplicationRunner applicationRunner() {
        return applicationArguments -> {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");
//有返回值,且主線程須要用到返回值
            System.out.println(Thread.currentThread().getName() + ":返回值:" + testService.asyncTask("huanzi-qch").get());

            long endTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime));
        };
    }

  能夠發現,有返回值的狀況下,雖然異步業務邏輯是由新線程執行,但若是在主線程操做返回值對象,主線程會等待,仍是順序執行 

 

  事務測試

  爲了方便觀察、測試,咱們在配置文件中將日誌級別設置成debug

#修改日誌登記,方便調試
logging.level.root=debug

 

  事務提交

    /**
     * 啓動成功
     */
    @Bean
    public ApplicationRunner applicationRunner() {
        return applicationArguments -> {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");//事務測試,事務正常提交
            testService.asyncTaskForTransaction(false);

            long endTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime));
        };
    }

 

  模擬異常,事務回滾

    /**
     * 啓動成功
     */
    @Bean
    public ApplicationRunner applicationRunner() {
        return applicationArguments -> {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":開始調用異步業務");
//事務測試,模擬異常事務回滾
            testService.asyncTaskForTransaction(true);

            long endTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + ":調用異步業務結束,耗時:" + (endTime - startTime));
        };
    }

 

 

  後記

  SpringBoot使用@Async優雅的異步調用就暫時記錄到這裏,之後再進行補充

 

  代碼開源

  代碼已經開源、託管到個人GitHub、碼雲:

  GitHub:https://github.com/huanzi-qch/springBoot

  碼雲:https://gitee.com/huanzi-qch/springBoot

相關文章
相關標籤/搜索