《SpringBoot2.0 實戰》系列-異步多線程調用

前言:

異步在工作中越來越多的被使用到,比如:推送提醒、服務之間調用、數據同步等。最近我就遇到一個需求,【發佈公告的時候需要調用第三方的接口發送短信】,這時我們就可以用異步實現短信發送,即可保證接口的響應速度,又可保證接口不受三方接口的超時、異常影響。

聊聊異步和同步:

同步:一件事一件事的做;【喫飯、睡覺、打豆豆】就是一個同步的過程;
異步:多件事一起做;【邊喫邊聊】就是一個異步的過程;

舉得例子可能不太恰當,只是爲了讓大家更好的理解。下面我們就進入正題。

增加核心配置類:

@EnableAsync可加載啓動類上,也可加載配置類上

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

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

/**
 * 異步線程池配置
 * 
 * @author gourd
 */
@Configuration
@EnableAsync
public class TaskPoolConfig implements AsyncConfigurer {

    @Bean("asyncExecutor")
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心線程數(默認線程數)
        executor.setCorePoolSize(10);
        // 最大線程數
        executor.setMaxPoolSize(20);
        // 緩衝隊列數
        executor.setQueueCapacity(200);
        // 允許線程空閒時間(單位:默認爲秒)
        executor.setKeepAliveSeconds(60);
        // 線程池名前綴
        executor.setThreadNamePrefix("taskExecutor-");
        // 設置是否等待計劃任務在關閉時完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 設置此執行器應該阻止的最大秒數
        executor.setAwaitTerminationSeconds(60);
        // 增加 TaskDecorator 屬性的配置
        executor.setTaskDecorator(new ContextDecorator());
        // 線程池對拒絕任務的處理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        executor.initialize();
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }


    /**
     * 任務裝飾器
     */
    class ContextDecorator implements TaskDecorator {
        @Override
        public Runnable decorate(Runnable runnable) {
            RequestAttributes context = RequestContextHolder.currentRequestAttributes();
            return () -> {
                try {
                    RequestContextHolder.setRequestAttributes(context);
                    runnable.run();
                } finally {
                    RequestContextHolder.resetRequestAttributes();
                }
            };
        }
    }
}

service業務邏輯:

1.在需要異步的方法上加上  @Async 註解就可以了;

2.如果不想抽出一個異步方法,也可以直接使用線程池,下文測試有demo。

import com.gourd.common.async.service.AsyncService;
import com.gourd.common.data.BaseResponse;
import com.gourd.common.utils.RequestHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Future;

/**
 * @author gourd
 */
@Service
@Slf4j
public class AsyncServiceImpl implements AsyncService {

    @Override
    @Async
    public Future<BaseResponse> doTaskOne() {
        log.info("開始做任務一(睡眠2s)");
        methodThread();
        HttpServletRequest request = RequestHolder.getRequest();
        log.info("任務一完成,當前線程爲 {},請求方法爲 {},請求路徑爲:{}", Thread.currentThread().getName(), request.getMethod(), request.getRequestURL().toString());
        return new AsyncResult<>(BaseResponse.ok("任務一完成"));
    }

    @Override
    @Async
    public Future<BaseResponse> doTaskTwo(){
        log.info("開始做任務二(睡眠2s)");
        methodThread();
        HttpServletRequest request = RequestHolder.getRequest();
        log.info("任務二完成,當前線程爲 {},請求方法爲 {},請求路徑爲:{}", Thread.currentThread().getName(), request.getMethod(), request.getRequestURL().toString());
        return new AsyncResult<>(BaseResponse.ok("任務二完成"));
    }

    @Override
    @Async
    public void doTaskThree()  {
        log.info("開始做任務三(睡眠1s)");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.interrupted();
            log.error("sleep異常:{}", e);
        }
        HttpServletRequest request = RequestHolder.getRequest();
        log.info("任務三完成,當前線程爲 {},請求方法爲 {},請求路徑爲:{}", Thread.currentThread().getName(), request.getMethod(), request.getRequestURL().toString());
    }


    /**
     * 接口睡眠
     */
    private void methodThread() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.interrupted();
            log.error("sleep異常:{}", e);
        }
    }
}

BaseResponse實體類:

import lombok.Data;
import org.springframework.http.HttpStatus;

import java.io.Serializable;

/**
 * @Author: gourd
 * @Description:
 * @Date:Created in 2018/8/27 16:19.
 */
@Data
public class BaseResponse<T> implements Serializable {
    private int code;
    private String msg;
    private T data;
    private String token;
    public BaseResponse() {

    }
    public BaseResponse(int code, String msg, T data, String token) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.token = token;
    }
    public BaseResponse(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public BaseResponse(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }


    public BaseResponse(int code, String msg, String token) {
        this.code = code;
        this.msg = msg;
        this.token = token;
    }

    public static BaseResponse ok(Object o) {
        return new BaseResponse(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase(),o);
    }
    public static BaseResponse ok(String msg) {
        return new BaseResponse(HttpStatus.OK.value(), msg);
    }

    public static BaseResponse ok(String msg,Object o) {
        return new BaseResponse(HttpStatus.OK.value(), msg,o);
    }
    public static BaseResponse failure(String msg) {
        return new BaseResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg);
    }

    public static BaseResponse failure( Object o) {
        return new BaseResponse(HttpStatus.BAD_REQUEST.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),o);
    }


}

Controller接口測試

import com.gourd.common.async.service.AsyncService;
import com.gourd.common.data.BaseResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Future;

/**
 * @author gourd
 * createAt: 2018/9/17
 */

@RestController
@Api(tags = "async",description = "異步任務控制器")
@RequestMapping("/async")
@Slf4j
public class AsyncTaskController {

    @Autowired
    private AsyncService asyncService;

    /**
     * 直接使用異步線程池
     */
    @Autowired
    private Executor asyncExecutor;

    @GetMapping(value = "/task" )
    @ApiOperation(value = "異步任務控制器", notes = "異步任務控制器")
    public BaseResponse taskExecute(){
        long startTime = System.currentTimeMillis();
        try {
            Future<BaseResponse> r1 = asyncService.doTaskOne();
            Future<BaseResponse> r2 = asyncService.doTaskTwo();
            asyncService.doTaskThree();
            // 異步線程池執行
            asyncExecutor.execute(() -> log.info("^o^============異步線程池執行...."));
            while (true) {
                if (r1.isDone() && r2.isDone()) {
                    log.info("異步任務一、二已完成");
                    break;
                }
            }
            BaseResponse baseResponse1 = r1.get();
            BaseResponse baseResponse2 = r2.get();
            log.info("返回結果:{},{}",baseResponse1 , baseResponse2);
        } catch (Exception e) {
            log.error("執行異步任務異常 {}",e.getMessage());
        }
        long endTime = System.currentTimeMillis();
        log.info("異步任務總耗時:{}",endTime-startTime);
        return BaseResponse.ok("異步任務全部執行成功");
    }
}

結果分析:

點擊運行後的打印結果:

按正常同步的情況,應該是:【任務一】-> 【任務二】-> 【任務三】,且接口總耗時應該是2+2+1 = 5秒左右,但是實際的運行情況只有2秒左右,所以說明了三個任務是異步執行的。任務一和任務二是有返回值得所以用了一個while(true)監控兩個任務都執行完後取到返回值,再去處理後續的邏輯。

 

測試案例流程圖

 

結尾:

本文是最近實踐異步多線程的一些總結和記錄,如有不對的地方,歡迎評論吐槽。

===============================================

代碼均已上傳至本人的開源項目

spring-cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章