多執行緒的使用(springboot)

預備知識

業務使用多執行緒的原因

  • 目的是面對高並發的時候,提高運行速度

場景一:

一個業務邏輯有很多次的循環,每次循環之間沒有影響,比如驗證1萬條url路徑是否存在,正常情況要循環1萬次,逐個去驗證每一條URL,這樣效率會很低,假設驗證一條需要1分鐘,總共就需要1萬分鐘,有點恐怖。這時可以用多執行緒,將1萬條URL分成50等份,開50個執行緒,沒個執行緒只需驗證200條,這樣所有的執行緒執行完是遠小於1萬分鐘的。

場景二:

需要知道一個任務的執行進度,比如我們常看到的進度條,實現方式可以是在任務中加入一個整型屬性變數(這樣不同方法可以共享),任務執行一定程度就給變數值加1,另外開一個執行緒按時間間隔不斷去訪問這個變數,並回饋給用戶。總之使用多執行緒就是為了充分利用cpu的資源,提高程式執行效率,當你發現一個業務邏輯執行效率特別低,耗時特別長,就可以考慮使用多執行緒。

問題:不過CPU執行哪個執行緒的時間和順序是不確定的,即使設置了執行緒的優先順序,因此使用多執行緒的風險也是比較大的,會出現很多預料不到的問題,一定要多熟悉概念,多構造不同的場景去測試才能夠掌握!

項目中可以通過:

@Order()
設置運行的優先順序,數字越小,級別越高

FutureTask介紹

參考: (101條消息) FutureTask詳解_索碼理的部落格-CSDN部落格_futuretask

執行緒池為什麼要使用阻塞隊列

阻塞隊列可以保證任務隊列中沒有任務時阻塞獲取任務的執行緒,使得執行緒進入wait 狀態,釋放 cpu 資源,當隊列中有任務時才喚醒對應執行緒從隊列中取出消息進行執行。
使得在執行緒不至於一直佔用cpu資源。(執行緒執行完任務後通過循環再次從任務隊列中取出任務進行執行,程式碼片段如:while (task != null || (task = getTask()) != null) {})。

不用阻塞隊列也是可以的,不過實現起來比較麻煩而已,有好用的為啥不用呢

Spring執行緒池

介紹:

Spring 通過任務執行器(TaskExecutor)來實現多執行緒和並發編程,使用 ThreadPoolTaskExecutor 實現一個基於執行緒池的TaskExecutor,
還得需要使用 @EnableAsync 開啟非同步,並通過在需要的非同步方法那裡使用註解@Async聲明是一個非同步任務
Spring 已經實現的異常執行緒池:

  • SimpleAsyncTaskExecutor:不是真的執行緒池,這個類不重用執行緒,每次調用都會創建一個新的執行緒。
  • SyncTaskExecutor:這個類沒有實現非同步調用,只是一個同步操作。只適用於不需要多執行緒的地方
  • ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類
  • SimpleThreadPoolTaskExecutor:是Quartz的 SimpleThreadPool 的類。執行緒池同時被quartz和非quartz使用,才需要使用此類
  • ThreadPoolTaskExecutor :最常使用,推薦。 其實質是對 java.util.concurrent.ThreadPoolExecutor 的包裝

同步使用

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
FutureTask<List> commentCallable = new FutureTask<>(GrouponRulesCallable);
Future<Map> submit = executor.submit(commentCallable);
Map map = submit.get();

非同步的使用:

[(101條消息) SpringBoot實現多執行緒_喜歡前端的後端MelodyJerry的部落格-CSDN部落格_springboot實現多執行緒](//blog.csdn.net/weixin_43438052/article/details/116108860#:~:text=SpringBoot實現 多執行緒 1 SpringBoot通過 任務執行器TaskExecutor 來實現多執行緒和並發編程。 2 使用,%40EnableAsync 開啟對 非同步任務 的支援,並通過在實際執行的Bean中的方法使用 %40Async註解 來聲明這是一個 非同步任務 。)

JUC 的執行緒池

並發下使用同步執行緒

具體程式碼參考: litemall示例程式碼

  • 聲明
    private final static ArrayBlockingQueue<Runnable> WORK_QUEUE= new ArrayBlockingQueue<>(9);
    // handler – 由於達到執行緒邊界和隊列容量而阻塞執行時使用的處理程式
    private final static RejectedExecutionHandler HANDLER=new ThreadPoolExecutor.CallerRunsPolicy();

    private static ThreadPoolExecutor executorService = new ThreadPoolExecutor(16, 16, 1000, TimeUnit.MILLISECONDS, WORK_QUEUE, HANDLER);

  • 使用,通過 get() 方法獲取他的返回值
//        商品屬性
        Callable<List> listCallable  =()->goodsAttributeService.getGoodsAttributeList(litemallGoods.getGoodsSn());

//        商品規格
        Callable<Object> specificationCallable =()->  specificationService.getGoodsSpecification(litemallGoods.getGoodsSn());


        FutureTask<List> goodsAttributeListTask = new FutureTask<>(listCallable);
        FutureTask<Object> objectCallableTask = new FutureTask<>(specificationCallable);
        
        executorService.submit(goodsAttributeListTask);
        executorService.submit(objectCallableTask);