欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot的調(diào)度服務(wù)與異步服務(wù)使用詳解

 更新時間:2025年02月24日 15:08:56   作者:jforgame  
本文主要介紹了Java的ScheduledExecutorService接口和Spring Boot中如何使用調(diào)度線程池,包括核心參數(shù)、創(chuàng)建方式、自定義線程池、Cron表達式,以及如何在Spring Boot中配置和使用異步任務(wù),此外,還討論了如何模擬系統(tǒng)繁忙和調(diào)整異步線程池的拒絕策略

1.調(diào)度服務(wù)

1.1.JDK之ScheduledExecutorService

講到調(diào)度任務(wù),我們腦海里馬上會想到ScheduledExecutorService。

ScheduledExecutorService是 Java java.util.concurrent 包中的一個接口,它繼承自 ExecutorService 接口。它主要用于在給定的延遲后運行任務(wù),或者定期地執(zhí)行任務(wù)。這個接口提供了幾種安排任務(wù)執(zhí)行的方法,包括單次執(zhí)行、定期執(zhí)行和周期性執(zhí)行。

以下是 ScheduledExecutorService 提供的一些關(guān)鍵方法:

  • schedule(Callable<V> callable, long delay, TimeUnit unit): 安排所提交的 Callable 任務(wù)在指定的延遲后運行,返回一個 Future,代表任務(wù)的結(jié)果。
  • schedule(Runnable command, long delay, TimeUnit unit): 安排所提交的 Runnable 任務(wù)在指定的延遲后運行。
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit): 安排所提交的 Runnable 任務(wù)在指定的初始延遲后首次啟動,并且隨后按指定的周期重復(fù)執(zhí)行。
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit): 安排所提交的 Runnable 任務(wù)在指定的初始延遲后首次啟動,并且隨后在每次執(zhí)行結(jié)束和下次執(zhí)行開始之間都存在指定的延遲。

然而,如果采用了sprintboot,我們也可以直接采用springboot提供的調(diào)試線程池。這其中有一個最大的優(yōu)勢在于,可以利用spring的cron表達式,采用方法注解的方式,不用引入quartz第三方工具。

1.2.springboot使用調(diào)度線程池

我們嘗試從源代碼的角度,來看看springboot提供的調(diào)度線程池是怎么創(chuàng)建的。

首先,啟用調(diào)度配置,啟動類加上@EnableScheduling

springboot的自動配置類一般以AutoConfiguration作為后綴,采用模糊搜索ScheduleAutoConfiguration可以找到目標TaskSchedulingAutoConfiguration。

從截圖我們可以看出,我們只需在application.yml加入以下的配置就可以啟動了

spring:
  task:
    ## 定時任務(wù)(業(yè)務(wù)上定時任務(wù)量不多,2個足矣)
    scheduling:
      ## 線程池核心線程數(shù)量
      pool:
        size: 2
      ## 線程名稱前線
      threadNamePrefix: common-scheduling-

spring對線程池進行二次封閉,最終調(diào)用的還是jdk的ThreadPoolExecutor類,我們在該類的構(gòu)造函數(shù)打個斷點,可以看到,pool.size參數(shù)已經(jīng)被傳參:

1.3.ThreadPoolExecutor核心參數(shù)與執(zhí)行流程

這里有必要先介紹下 ThreadPoolExecutor類的幾個函數(shù)參數(shù)及基本運行機制

構(gòu)造函數(shù)參數(shù):

  • 核心線程數(shù)(Core Pool Size): 線程池中始終保持的線程數(shù)量,即使它們處于空閑狀態(tài)。如果任務(wù)數(shù)量少于核心線程數(shù),線程池會創(chuàng)建新的線程來處理任務(wù),而不會立即回收這些線程。
  • 最大線程數(shù)(Maximum Pool Size): 線程池中允許的最大線程數(shù)量。如果任務(wù)數(shù)量超過了核心線程數(shù)但小于最大線程數(shù),且工作隊列已滿,線程池會創(chuàng)建新的線程來處理任務(wù),直到達到最大線程數(shù)。
  • 工作隊列(Work Queue): 用于存放待執(zhí)行任務(wù)的阻塞隊列。當所有核心線程都在忙碌時,新的任務(wù)會被放入工作隊列中等待執(zhí)行。
  • 線程工廠(Thread Factory): 用于創(chuàng)建新線程的工廠。它提供了一種方式來定制線程的創(chuàng)建過程,例如設(shè)置線程的名稱、優(yōu)先級、是否為守護線程等。
  • 拒絕策略(Rejected Execution Handler): 當任務(wù)無法被線程池及時處理時(即當線程池已滿,且工作隊列已滿),線程池會采用拒絕策略來處理新提交的任務(wù)。常見的拒絕策略包括:
  • AbortPolicy:拋出 RejectedExecutionException。
  • CallerRunsPolicy:由調(diào)用者線程運行該任務(wù)。
  • DiscardPolicy:靜默丟棄任務(wù)。
  • DiscardOldestPolicy:丟棄隊列中最老的任務(wù),然后嘗試再次提交當前任務(wù)。
  • 保持活動時間(Keep-Alive Time): 非核心線程空閑時在終止前等待新任務(wù)的最長時間。如果線程池允許核心線程空閑,這個參數(shù)也適用于核心線程。
  • 時間單位(Time Unit): 與保持活動時間配合使用的時間單位,例如 TimeUnit.SECONDS。

執(zhí)行流程:

  • 如果線程池中的線程數(shù)量少于核心線程數(shù),即使有空閑線程,線程池也會優(yōu)先創(chuàng)建新線程來執(zhí)行新的任務(wù)。
  • 如果線程池中的線程數(shù)量達到核心線程數(shù),新的任務(wù)會被放入工作隊列等待執(zhí)行。
  • 如果工作隊列已滿且線程數(shù)量少于最大線程數(shù),線程池會創(chuàng)建新的非核心線程來執(zhí)行任務(wù)。
  • 如果工作隊列已滿且線程數(shù)量達到最大線程數(shù),新的任務(wù)會被拒絕,線程池會采用拒絕策略來處理。

1.4.自定義線程池

由于spingboot對調(diào)度任務(wù)線程池的參數(shù)支持有限,如果想定制自己的參數(shù),可以注入自己的調(diào)度線程池,從代碼可看出:

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ScheduledExecutorService scheduledExecutorService() {
        return new ScheduledThreadPoolExecutor(2,
                new NamedThreadFactory("common-schedule"),
                new ThreadPoolExecutor.CallerRunsPolicy()) {
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
            }
        };
    }

}

1.5.spring cron注解

Cron 表達式是一種用于描述定時任務(wù)觸發(fā)時間的字符串表達式。它由多個時間字段組成,每個字段代表定時任務(wù)在特定時間單位上的觸發(fā)條件。

1.5.1.cron表達式語法格式

秒 分 時 日 月 星期 年份

其中,每個時間字段都有對應(yīng)的取值范圍和特殊符號。下面是每個時間字段的詳細說明:

1、秒(Seconds):取值范圍為 0~59。例如,`0/5` 表示從0秒開始,每隔 5 秒觸發(fā)一次,`*` 表示每秒都觸發(fā)。

2、分鐘(Minutes):取值范圍為 0~59。例如,`0/5` 表示從0分鐘開始,每隔 5 分鐘觸發(fā)一次,`*` 表示每分鐘都觸發(fā)。

3、小時(Hours):取值范圍為 0~23。例如,`0/2` 表示從0小時開始,每隔 2 小時觸發(fā)一次,`*` 表示每小時都觸發(fā)。

4、日期(Day of Month):取值范圍為 1~31。例如,`1,15` 表示每月的 1 日和 15 日觸發(fā),`*` 表示每天都觸發(fā)。

5、月份(Month):取值范圍為 1~12,也可以使用英文縮寫 JAN、FEB、MAR 等。例如,`1,6` 表示一月和六月觸發(fā),`*` 表示每個月都觸發(fā)。

6、 星期(Day of Week):取值范圍為 1~7,1 表示星期日,2 表示星期一,以此類推,也可以使用英文縮寫 SUN、MON、TUE 等。例如,`2-6` 表示星期一到星期五觸發(fā),`*` 表示每個星期都觸發(fā)。

7、年份(Year):可選字段,表示觸發(fā)條件的年份。例如,`2023` 表示在 2023 年觸發(fā),`*` 表示每年都觸發(fā)。

除了取值范圍,Cron 表達式還支持一些特殊符號,用于指定特定的觸發(fā)條件,例如:

  • - 星號(*):代表所有可能的取值,表示不限制該時間字段的取值范圍。
  • - 問號(?):僅在日期和星期字段中使用,表示不指定具體的取值,可以任意匹配。
  • - 斜線(/):表示間隔觸發(fā),例如在分鐘字段中,"*/5" 表示每隔 5 分鐘觸發(fā)一次。
  • - 逗號(,):用于指定多個取值,例如在小時字段中,"1,3,5" 表示在第 1、3、5 小時觸發(fā)。
  • - 減號(-):用于指定一個范圍,例如在月份字段中,"3-6" 表示三月到六月觸發(fā)。
  • L : 表示最后,只能出現(xiàn)在星期和每月第幾天域,如果在星期域使用1L,意味著在最后的一個星期日觸發(fā)。
  • W : 表示有效工作日(周一到周五),只能出現(xiàn)在每月第幾日域,系統(tǒng)將在離指定日期的最近的有效工作日觸發(fā)事件。注意一點,W的最近尋找不會跨過月份
  • LW : 這兩個字符可以連用,表示在某個月最后一個工作日,即最后一個星期五。

# : 用于確定每個月第幾個星期幾,只能出現(xiàn)在每月第幾天域。例如在1#3,表示某月的第三個星期日。

1.5.2.cron表達式示例

作用表達式
每隔5秒執(zhí)行一次*/5 * * * * ?
每天中午12點執(zhí)行一次0 0 12 * * ?

2024年的每天上午10:00執(zhí)行一次
0 0 10 * * ? 2023
每天下午6點到下午6:59每分鐘執(zhí)行一次0 * 18 * * ?
每月的最后一個星期五上午10:30執(zhí)行一次0 30 10 ? * 6L
每月的第4個星期五上午10:25執(zhí)行一次0 25 10 ? * 6#4
每天上午8點,下午1點,4點執(zhí)行一次0 0 8,13,16 * * ?

2.異步任務(wù)

2.1.springboot配置

在軟件開發(fā)中,有些任務(wù)比較耗時但又無需馬上獲得結(jié)果。一般地,這些任務(wù)我們可以采用獨立線程池異步執(zhí)行。如果程序基于springboot環(huán)境,我們有現(xiàn)成的工具可以使用。

首先,我們需要在程序啟動入口類增加@EnableAsync。

借著,我們嘗試從源代碼的角度,來看看springboot提供的異步線程池是怎么創(chuàng)建的。

從springboot的命名風格可知,通過模糊搜索TaskAutoConfiguration,可以找到TaskExecutionAutoConfiguration,如下:

從源代碼可知,只需在application.yml配置如下參數(shù)即可:

spring:    
    execution:
      pool:
        coreSize: 4
        queueCapacity: 64
        maxSize: 8
        ## 禁止空閑線程關(guān)閉,保證最少有core個存活線程
        allowCoreThreadTimeout: false
        keepAlive: 300s
      threadNamePrefix: common-async_task-

這些參數(shù)跟jdk的ThreadPoolExecutor類的構(gòu)造參數(shù)非常相似,這里不作解析 。

2.2.使用異步任務(wù)

使用方法,只要在目標方法的簽名加上@Async

然而,執(zhí)行結(jié)果卻出乎意外(在main主線程上執(zhí)行,沒有異步執(zhí)行)

熟悉springaop機制的同學(xué),馬上知道這是因為異步任務(wù)底層是基于動態(tài)代理機制實現(xiàn)的。Spring AOP 代理只有在通過 Spring 容器獲取 Bean 時才會創(chuàng)建。

當在同一個類內(nèi)部調(diào)用一個方法時,調(diào)用的是原始對象,而不是代理對象。因此,內(nèi)部調(diào)用不會經(jīng)過 Spring AOP 代理,也就無法觸發(fā)異步執(zhí)行。解決這個問題最簡單的方法是用一個新的類來管理異步執(zhí)行方法。

問題解決

2.3.模擬系統(tǒng)繁忙進行性能測試

我們嘗試模擬一些極端情況,系統(tǒng)處理不過來的情況。修改上面的配置,改為

pool:
  coreSize: 1
  queueCapacity: 1
  maxSize: 8

同時,異步執(zhí)行增加時間延遲來模擬耗時任務(wù)

@Component
@Slf4j
class AsyncTaskHandler {

    @Async
    public void busyTask1() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ignored) {
        }
        log.info("----------busyTask1---------");
    }

    @Async
    public void busyTask2() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ignored) {
        }
        log.info("----------busyTask2---------");
    }

}

執(zhí)行結(jié)果會出現(xiàn)報錯(線程數(shù)已達到最大數(shù)量,且任務(wù)隊列已滿,無法添加新任務(wù))

調(diào)度任務(wù)執(zhí)行頻率是可預(yù)見的,有多少個任務(wù),執(zhí)行頻率,開發(fā)可知,核心數(shù)量,最大數(shù)量,隊列容量比較好設(shè)定。而異步執(zhí)行頻率很大程度是由系統(tǒng)的使用者(用戶)決定的,因此這些參數(shù)需要根據(jù)流量動態(tài)修改。

2.4.異步線程池拒絕策略

如果不想把 queueCapacity和maxSize都設(shè)置成很大的話,我們可以考慮修改下線程池的拒絕策略。最妥當?shù)姆绞绞?,既然異步不了,那?quot;熔斷"成同步,直接在調(diào)用者所在的業(yè)務(wù)線程執(zhí)行。然而,springboot沒有提供相應(yīng)的配置項。為此,我們只能關(guān)閉springboot的自動配置了。

從代碼可看出,只要提供一個Executor實例,并且名字叫taskExecutor即可。

話不多說,上代碼

@Configuration
public class ThreadPoolConfig {

    private final int core = Runtime.getRuntime().availableProcessors();

    /**
     * springboot 自動注入的異步執(zhí)行線程池,拒絕策略為丟棄,難以配置maxPoolSize參數(shù)
     * @see TaskExecutionAutoConfiguration#taskExecutorBuilder
     */
    @Bean(name = "taskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(TaskExecutionProperties properties) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(properties.getPool().getCoreSize());
        executor.setThreadNamePrefix(properties.getThreadNamePrefix());
        executor.setMaxPoolSize(properties.getPool().getMaxSize());
        executor.setQueueCapacity(properties.getPool().getQueueCapacity());
        executor.setKeepAliveSeconds((int) properties.getPool().getKeepAlive().toSeconds());
        executor.setAllowCoreThreadTimeOut(properties.getPool().isAllowCoreThreadTimeout());
        // 超過隊列容量,則在業(yè)務(wù)線程上執(zhí)行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

}

重新運行程序,可以看到,當任務(wù)超過線程池負載的時候,多余的線程會在調(diào)用線程上執(zhí)行,變?yōu)橥酱a

個人認為:使用springboot創(chuàng)建的線程池,代碼也只是稍微簡化一點點。

采用原生線程池,每個業(yè)務(wù)代碼必須實現(xiàn)Runnable接口,而使用springboot的異步線程池,只需以方法注解的形式即可,底層aop會生成對應(yīng)的代理方法。但要確保避免內(nèi)部方法調(diào)用導(dǎo)致異步邏輯失效。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • logback ThresholdFilter臨界值日志過濾器源碼解讀

    logback ThresholdFilter臨界值日志過濾器源碼解讀

    這篇文章主要為大家介紹了logback ThresholdFilter臨界值日志過濾器源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • 基于Java子線程中的異常處理方法(通用)

    基于Java子線程中的異常處理方法(通用)

    下面小編就為大家?guī)硪黄贘ava子線程中的異常處理方法(通用)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • 基于Spring Data Jest的Elasticsearch數(shù)據(jù)統(tǒng)計示例

    基于Spring Data Jest的Elasticsearch數(shù)據(jù)統(tǒng)計示例

    本篇文章主要介紹了基于Spring Data Jest的Elasticsearch數(shù)據(jù)統(tǒng)計示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • idea運行java項目main方法報build failure錯誤的解決方法

    idea運行java項目main方法報build failure錯誤的解決方法

    當在使用 IntelliJ IDEA 運行 Java 項目的 main 方法時遇到 "Build Failure" 錯誤,這通常意味著在項目的構(gòu)建過程中遇到了問題,以下是一些詳細的解決步驟,以及一個簡單的代碼示例,用于展示如何確保 Java 程序可以成功構(gòu)建和運行,需要的朋友可以參考下
    2024-09-09
  • SpringBoot JSON全局日期格式轉(zhuǎn)換器實現(xiàn)方式

    SpringBoot JSON全局日期格式轉(zhuǎn)換器實現(xiàn)方式

    這篇文章主要介紹了SpringBoot JSON全局日期格式轉(zhuǎn)換器,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • 代碼分析JAVA中PCM人聲音頻變聲處理

    代碼分析JAVA中PCM人聲音頻變聲處理

    本篇文章通過代碼實例給大家分析了JAVA中PCM人聲音頻變聲處理的問題,有興趣的朋友跟著學(xué)習分考下吧。
    2018-01-01
  • JAVA+Struts2獲取服務(wù)器地址的方法

    JAVA+Struts2獲取服務(wù)器地址的方法

    這篇文章主要介紹了JAVA+Struts2獲取服務(wù)器地址的方法,是Struts2的一個簡單應(yīng)用,具有一定的借鑒與參考價值,需要的朋友可以參考下
    2014-11-11
  • Netty分布式ByteBuf中PooledByteBufAllocator剖析

    Netty分布式ByteBuf中PooledByteBufAllocator剖析

    這篇文章主要為大家介紹了Netty分布式ByteBuf剖析PooledByteBufAllocator簡述,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • java將一個目錄下的所有文件復(fù)制n次

    java將一個目錄下的所有文件復(fù)制n次

    這篇文章主要為大家詳細介紹了java將一個目錄下的所有文件復(fù)制n次,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • Java通過exchange協(xié)議發(fā)送郵件

    Java通過exchange協(xié)議發(fā)送郵件

    這篇文章主要為大家詳細介紹了Java通過exchange協(xié)議發(fā)送郵件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-02-02

最新評論