前言
有這樣的一個需求:使用者需要上傳一份的excel資料,如果行數非常多,或者解析比較複雜,如果不做異步處理,那麼使用者可能要等十幾秒,甚至更多時間。為了讓使用者有更好的體驗,我們可以先傳回結果給使用者,而資料校驗及入庫則由一個子線程異步進行。
定義線程池
解決上面的問題,java提供一個非常好用的技術方案,springboot更是對其進一步進行封裝,下面定義了一個上傳線程池:
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.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class SyncConfiguration {
@Bean(name = "uploadExcelPoolTaskExecutor")
public ThreadPoolTaskExecutor uploadExcelPoolTaskExecutor(){
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心線程數
taskExecutor.setCorePoolSize(10);
//最大線程數
taskExecutor.setMaxPoolSize(100);
//用于儲存任務的隊列
taskExecutor.setQueueCapacity(100);
//非核心線程的空閑時間超過keepAliveTime就會被自動終止回收掉
taskExecutor.setKeepAliveSeconds(200);
//線程池名的字首
taskExecutor.setThreadNamePrefix("upload-excel-");
/**
* AbortPolicy:中斷抛出異常
* DiscardPolicy:默默丢棄任務,不進行任何通知
* DiscardOldestPolicy:丢棄掉在隊列中存在時間最久的任務,然後重試任務
* CallerRunsPolicy:重試添加目前的任務,自動重複調用
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
taskExecutor.initialize();
return taskExecutor;
}
}
@Configuration:定義一個配置類,裡面可以包含多個Bean注解的方法。例如上述檔案中還可以定義其它業務的子線程
@EnableAsync: 跟配置注解@Configuration結合 , 使用的時候@Async加上方法的Bean名稱
CorePoolSize:核心線程數,也是線程池中常駐的線程數,線程池初始化時預設是沒有線程的,當任務來臨時才開始建立線程去執行任務
MaxPoolSize:最大線程數,在核心線程數的基礎上可能會額外增加一些非核心線程,需要注意的是隻有當workQueue隊列填滿時才會建立多于corePoolSize的線程(線程池總線程數不超過maxPoolSize)
QueueCapacity:用于儲存任務的隊列,可以為無界、有界、同步移交三種隊列類型之一,當池子裡的工作線程數大于corePoolSize時,這時新進來的任務會被放到隊列中
KeepAliveSeconds:非核心線程的空閑時間超過keepAliveTime就會被自動終止回收掉,注意當corePoolSize=maxPoolSize時,keepAliveTime參數也就不起作用了(因為不存在非核心線程)
ThreadNamePrefix:線程池名的字首
RejectedExecutionHandler:異常處理
想深入了解,可以看源碼
使用示例
業務層僞代碼:
@Service
@Slf4j
public class UploadService {
//這裡需要跟@Bean(name = "uploadExcelPoolTaskExecutor")對應上
@Async("uploadExcelPoolTaskExecutor")
public void insertData() {
try {
//這裡寫入庫的邏輯
log.info("導入中……");
Thread.sleep(5000);
log.info("導入成功");
}catch (Exception e){
log.info("導入失敗");
}
}
}
控制器:
@RestController
@RequestMapping("/test")
@Slf4j
public class Test{
@Resource
UploadService uploadService;
@GetMapping("/index")
public Result<Integer> index() {
//記錄上傳者資訊,代碼省略
//資料入庫
uploadService.insertData();
log.info("檔案上傳成功,資料正在導入中");
return Result.okWithMsg("檔案上傳成功,資料正在導入中");
}
}
執行結果如下:
2022-09-06 14:25:07.702 INFO-hello 29488 [http-nio-9093-exec-1] [TID: N/A] [] com.yfway.demo.user.controller.Test(34) : 檔案上傳成功,資料正在導入中
2022-09-06 14:25:07.705 INFO-hello 29488 [upload-excel-1] [TID: N/A] [] com.yfway.demo.user.service.UploadService(19) : 導入中……
2022-09-06 14:25:12.709 INFO-hello 29488 [upload-excel-1] [TID: N/A] [] com.yfway.demo.user.service.UploadService(21) : 導入成功
從結果中可以看出,先執行主線程,列印:“檔案上傳成功,資料正在導入中”
然後再實作子線程,列印“導入中……導入成功 ”