前言:
異步在工作中越來越多的被使用到,比如:推送提醒、服務之間調用、數據同步等。最近我就遇到一個需求,【發佈公告的時候需要調用第三方的接口發送短信】,這時我們就可以用異步實現短信發送,即可保證接口的響應速度,又可保證接口不受三方接口的超時、異常影響。
聊聊異步和同步:
同步:一件事一件事的做;【喫飯、睡覺、打豆豆】就是一個同步的過程;
異步:多件事一起做;【邊喫邊聊】就是一個異步的過程;
舉得例子可能不太恰當,只是爲了讓大家更好的理解。下面我們就進入正題。
增加核心配置類:
@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