天天看點

springboot利用線程池異步上傳檔案(1)

作者:小碼搬磚

前言

有這樣的一個需求:使用者需要上傳一份的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) : 導入成功           

從結果中可以看出,先執行主線程,列印:“檔案上傳成功,資料正在導入中”

然後再實作子線程,列印“導入中……導入成功 ”

繼續閱讀