SpringBoot使用線程池

1. 線程池

線程池是一種線程使用模式。線程過多會帶來額外的開銷,其中包括創(chuàng)建銷毀線程的開銷、調(diào)度線程的開銷等等,同時也降低了計算機的整體性能。線程池維護多個線程,等待監(jiān)督管理者分配可并發(fā)執(zhí)行的任務。這種做法,一方面避免了處理任務時創(chuàng)建銷毀線程開銷的代價,另一方面避免了線程數(shù)量膨脹導致的過分調(diào)度問題,保證了對內(nèi)核的充分利用。
使用線程池,有幾點好處:

  • 降低資源消耗:通過池化技術(shù)重復利用已創(chuàng)建的線程,降低線程創(chuàng)建和銷毀造成的損耗。
  • 提高響應速度:任務到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。
  • 提高線程的可管理性:線程是稀缺資源,如果無限制創(chuàng)建,不僅會消耗系統(tǒng)資源,還會因為線程的不合理分布導致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。

2. Java中線程池

Java開發(fā),我們使用JDK環(huán)境,開發(fā)框架基本都是Spring全家桶。線程池的基礎原理都在JDK中,JDK1.5新增線程池相關(guān)類,在核心jar包rt.jar中,java.util.concurrent下面。

2.1 JDK中線程池

看一下jdk中線程池繼承關(guān)系類圖:

線程UML.png

可以看出,包含三個線程池:ForkJoinPool,ThreadPoolExecutorScheduledThreadPoolExecutor。

  • ForkJoinPool是Java 1.7 引入的一種新的并發(fā)框架,核心思想是將大的任務拆分成多個小任務(fork),然后在將多個小任務處理匯總到一個結(jié)果上(join),充分利用多cpu,多核CPU的優(yōu)勢,引入了“work-stealing”機制,更有效的利用線程。
  • ThreadPoolExecutor是 java常用的線程池,提供基礎的線程池功能。初始化傳入不同類型的工作隊列和拒絕策略參數(shù),可以定制不同類型和功能的線程池,應用最為廣泛。
  • ScheduledThreadPoolExecutor 從類圖上可以看出,它繼承了ThreadPoolExecutor,并實現(xiàn)了ScheduledExecutorService,是對ThreadPoolExecutor做的功能擴展,本質(zhì)上是一個使用線程池執(zhí)行定時任務的類,可以用來在給定延時后執(zhí)行異步任務或者周期性執(zhí)行任務,較任務調(diào)度的Timer來說,其功能更加強大。

2.2 JDK中提供的幾類線程池

JDK中,使用ThreadPoolExecutor給創(chuàng)建了幾個不同功能種類的線程池,可以調(diào)出來看看:

 //創(chuàng)建一個核心線程數(shù)和最大線程數(shù)相同的線程池
 ExecutorService executorService = Executors.newFixedThreadPool(4);
 executorService.execute(()->{System.out.println("this is fixed thread pool");});
 //創(chuàng)建一個單線程的線程池
 ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
 singleExecutor.execute(() -> System.out.println("this is single thread pool"));
 //創(chuàng)建一個緩存線程池
ExecutorService cacheExecutor = Executors.newCachedThreadPool();
cacheExecutor.execute(()-> System.out.println("this is cache thread pool"));
//創(chuàng)建一個延時執(zhí)行任務的線程池
ScheduledExecutorService scheduleExecutor = Executors.newScheduledThreadPool(4);
scheduleExecutor.schedule(()-> System.out.println("this is schedule thread  pool"),2,TimeUnit.SECONDS);

看一下都有什么特點:

線程池名稱 使用阻塞隊列 特點
newFixedThreadPool LinkedBlockingQueue() 1、核心線程數(shù)和最大線程數(shù)相同
2、由于keepAliveTime設置為0,當線程創(chuàng)建后會一直存在
3、由于用的是無界隊列所以可能會導致OOM
newSingleThreadExecutor LinkedBlockingQueue() 1、核心線程數(shù)和最大線程數(shù)都為1單線程
2、無界隊列可能導致OOM
newCachedThreadPool SynchronousQueue() 1、核心線程數(shù)為0,最大線程數(shù)為Integer.MAX_VALUE
2、當沒任務時線程存活時間為60秒
3、使用的是0大小的隊列,所以不存儲任務,只做任務轉(zhuǎn)發(fā)
newScheduledThreadPool DelayedWorkQueue() 1、執(zhí)行周期任務
2、無界隊列,可能會導致OOM

從上面可以看出他們各有各的特點,但是阿里巴巴開發(fā)守則卻不推薦使用以上線程池,因為它們可能會對服務資源的浪費,所以推薦使用通過ThreadPoolExecutor自定線程池。
Spring中將Java中的線程池進行了封裝,而且提供了默認實現(xiàn),也能自定義線程池,我一般都用Spring中的線程池包。

3. Spring中的線程池

Spring中的線程池和JDK中的基本一樣,在org.springframework.scheduling.concurrent包下面。
和JDK中對應的,Spring的頂層接口是TaskExecutor,它實現(xiàn)了JDK中的Executor接口。
Spring中常用的線程池是ThreadPoolTaskExecutor,它是是借助于JDK并發(fā)包中的java.util.concurrent.ThreadPoolExecutor來實現(xiàn)的。
要想使用線程池,先了解一下線程池中的一些參數(shù):

3.1 線程池用到的一些參數(shù)

因為我們常用的是ThreadPoolExecutor線程池,所以去這個類中找。

參數(shù)

也就這幾個常用參數(shù),在下面使用中再介紹意思。

3.2 使用自定義線程池

  • 3.2.1 線程池配置類
package com.example.demo.threadpool;

import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import lombok.Data;

@Configuration
public class ThreadPoolConfig {
    
    ThreadPoolProperties properties = new ThreadPoolProperties();
    
    @Bean(name = "taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(properties.getCorePoolSize());
        executor.setMaxPoolSize(properties.getMaxPoolSize());
        executor.setQueueCapacity(properties.getQueueCapacity());
        executor.setThreadNamePrefix(properties.getThreadNamePrefix());
        // 設置線程保持活躍的時間(默認:60)
        executor.setKeepAliveSeconds(properties.getKeepAliveTime());
        // 當任務完成后,長時間無待處理任務時,銷毀線程池
        executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
        executor.setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds());
        // 設置任務拒絕策略
        /**
         * 4種
         * ThreadPoolExecutor類有幾個內(nèi)部實現(xiàn)類來處理這類情況:
            - AbortPolicy 丟棄任務,拋RejectedExecutionException
            - CallerRunsPolicy 由該線程調(diào)用線程運行。直接調(diào)用Runnable的run方法運行。
            - DiscardPolicy  拋棄策略,直接丟棄這個新提交的任務
            - DiscardOldestPolicy 拋棄舊任務策略,從隊列中踢出最先進入隊列(最后一個執(zhí)行)的任務
         * 實現(xiàn)RejectedExecutionHandler接口,可自定義處理器
         */
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
    
    @Data
    class ThreadPoolProperties {
        
        /**
         * 核心線程數(shù)(默認是1):若是IO密集型,cpu核心數(shù)*2,若是cpu密集型,cpu核心數(shù)
         * 核心線程會一直存活,及時沒有任務需要執(zhí)行
         * 設置allowCoreThreadTimeout=true(默認false)時,核心線程會超時關(guān)閉
         * 注意:當線程數(shù)小于核心線程數(shù)時,即使有線程空閑,線程池也會優(yōu)先創(chuàng)建新線程處理
         */
        private int corePoolSize = 8;  
        /** 
         * 最大線程數(shù),系統(tǒng)默認Integer.MAX_VALUE
         */  
        private int maxPoolSize = 20;  
        /** 
         * 允許線程空閑時間(單位:默認為秒,默認60S)
         * 當線程空閑時間達到keepAliveTime時,線程會退出,直到線程數(shù)量=corePoolSize
         * 如果allowCoreThreadTimeout=true,則會直到線程數(shù)量=0 
         */  
        private int keepAliveTime;  
        /** 
         * 緩沖隊列大小,系統(tǒng)默認Integer.MAX_VALUE
         * 注意:這個值肯定要改小,不然任務陡增時,都堆在隊列中(隊列值大),
         * 核心線程數(shù)就那幾個,無法很快執(zhí)行隊列中的任務,
         * 就會延長響應時間,阻塞任務
         */  
        private int queueCapacity = 6; 
        /** 
         * 線程池名前綴,用于監(jiān)控識別
         */  
        private String threadNamePrefix = "test";  
        
        /**
         * 允許核心線程超時關(guān)閉(默認false,核心線程不關(guān)閉)
         */
        private boolean allowCoreThreadTimeout = false;
        
        /**
         * 當任務完成后,長時間無待處理任務時,銷毀線程池,默認false,不銷毀
         */
        private boolean waitForTasksToCompleteOnShutdown = false;
        
        /**
        * 強制停止應用時,線程池等待時間
        * AwaitTerminationSeconds=0(默認為0)此時會立即停止線程池,
        * 任務也會被強制停止,可能導致任務失敗或丟失
        */
        private int awaitTerminationSeconds;
        
    }
}

上面配置了線程池,并生成了線程池bean,交給了Spring容器管理,使用時注入即可使用。

  • 3.2.2 使用線程池
    SpringBoot使用線程池注意2點:
    1. 添加@EnableAsync注解開啟異步,即多線程
    2. 在要使用的方法上加上@Async("taskExecutor")注解,括號內(nèi)是線程池名字
package com.example.demo.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;

@Service
@EnableAsync
public class ThreadPoolService {
    
    @Async("taskExecutor")
    public void F1() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
         for(int i = 0 ; i < 10 ; i++){
             System.out.println("A-"+i);
             Thread.sleep(500);
         }
         
    }
    
    @Async("taskExecutor")
    public void F2() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
         for(int i = 20 ; i < 30 ; i++){
            System.out.println("B-"+i);
            Thread.sleep(500);
        }
         
    }
}
  • 3.2.3 @Async失效情況:
    1. 異步方法使用static修飾
  • 2.異步類沒有使用@Component注解(或其他注解)導致spring無法掃描到異步類
    1. 異步方法不能與被調(diào)用的異步方法在同一個類中
    1. 類中需要使用@Autowired或@Resource等注解自動注入,不能自己手動new對象
    1. 如果使用SpringBoot框架必須在啟動類中增加@EnableAsync注解

3.3 SpringBoot中自動裝配的線程池

SpringBoot線程池自動裝配在spring-boot-autoconfigure這個jar中,在org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration類中。
自動裝配條件:

條件

自動裝配的Bean名字給了2個,一個是applicationTaskExecutor,一個是taskExecutor。如果自動裝配了,可以直接注入使用。
Bean

4. 線程池處理任務流程:

當往線程池中提交新任務時,線程池主要流程如下:
核心線程數(shù) -> 線程隊列 -> 最大線程數(shù) -> 拒絕策略

  1. 如果池中任務數(shù) < corePoolSize (核心線程數(shù)),創(chuàng)建新線程立即執(zhí)行任務
  2. 如果池中任務數(shù) > corePoolSize,新任務放到緩存隊列當中等待執(zhí)行
  3. 隊列滿,線程數(shù)量<maxPoolSize,新建線程立即執(zhí)行任務
  4. 隊列滿,線程數(shù)量>=maxPoolSize,使用拒絕策略拒絕

所以使用線程池,需要注意:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容