最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用ScheduledThreadPoolExecutor踩過最痛的坑

 更新時間:2023年08月10日 16:57:39   作者:肥肥技術(shù)宅  
這篇文章主要介紹了使用ScheduledThreadPoolExecutor踩過最痛的坑及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

概述

最近項目上反饋某個重要的定時任務(wù)突然不執(zhí)行了,很頭疼,開發(fā)環(huán)境和測試環(huán)境都沒有出現(xiàn)過這個問題。

定時任務(wù)采用的是 ScheduledThreadPoolExecutor ,后來一看代碼發(fā)現(xiàn)踩了一個大坑....

還原"大坑"

這個坑就是如果 ScheduledThreadPoolExecutor 中執(zhí)行的任務(wù)出錯拋出異常后,不僅不會打印異常堆棧信息,同時還會取消后面的調(diào)度 

直接看例子

@Test
public void testException() throws InterruptedException {
    // 創(chuàng)建1個線程的調(diào)度任務(wù)線程池
    ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
    // 創(chuàng)建一個任務(wù)
    Runnable runnable = new Runnable() {
        volatile int num = 0;
        @Override
        public void run() {
            num ++;
            // 模擬執(zhí)行報錯
            if(num > 5) {
                throw new RuntimeException("執(zhí)行錯誤");
            }
            log.info("exec num: [{}].....", num);
        }
    };
    // 每隔1秒鐘執(zhí)行一次任務(wù)
    scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
    Thread.sleep(10000);
}

運行結(jié)果:

  • 只執(zhí)行了5次后,就不打印,不執(zhí)行了,因為報錯了
  • 任務(wù)報錯,也沒有打印一次堆棧,更導(dǎo)致調(diào)度任務(wù)取消,后果十分嚴(yán)重。

解決方案

解決方法也非常簡單,只要通過try catch捕獲異常即可。

運行結(jié)果:

看到不僅打印了異常堆棧,而且也會進行周期性的調(diào)度。

更推薦的做法

更好的建議可以在自己的項目中封裝一個包裝類,要求所有的調(diào)度都提交通過我們統(tǒng)一的包裝類, 如下代碼:

@Slf4j
public class RunnableWrapper implements Runnable {
    // 實際要執(zhí)行的線程任務(wù)
    private Runnable task;
    // 線程任務(wù)被創(chuàng)建出來的時間
    private long createTime;
    // 線程任務(wù)被線程池運行的開始時間
    private long startTime;
    // 線程任務(wù)被線程池運行的結(jié)束時間
    private long endTime;
    // 線程信息
    private String taskInfo;
    private boolean showWaitLog;
    /**
     * 執(zhí)行間隔時間多久,打印日志
     */
    private long durMs = 1000L;
    // 當(dāng)這個任務(wù)被創(chuàng)建出來的時候,就會設(shè)置他的創(chuàng)建時間
    // 但是接下來有可能這個任務(wù)提交到線程池后,會進入線程池的隊列排隊
    public RunnableWrapper(Runnable task, String taskInfo) {
        this.task = task;
        this.taskInfo = taskInfo;
        this.createTime = System.currentTimeMillis();
    }
    public void setShowWaitLog(boolean showWaitLog) {
        this.showWaitLog = showWaitLog;
    }
    public void setDurMs(long durMs) {
        this.durMs = durMs;
    }
    // 當(dāng)任務(wù)在線程池排隊的時候,這個run方法是不會被運行的
    // 但是當(dāng)任務(wù)結(jié)束了排隊,得到線程池運行機會的時候,這個方法會被調(diào)用
    // 此時就可以設(shè)置線程任務(wù)的開始運行時間
    @Override
    public void run() {
        this.startTime = System.currentTimeMillis();
        // 此處可以通過調(diào)用監(jiān)控系統(tǒng)的API,實現(xiàn)監(jiān)控指標(biāo)上報
        // 用線程任務(wù)的startTime-createTime,其實就是任務(wù)排隊時間
        // 這邊打印日志輸出,也可以輸出到監(jiān)控系統(tǒng)中
        if(showWaitLog) {
            log.info("任務(wù)信息: [{}], 任務(wù)排隊時間: [{}]ms", taskInfo, startTime - createTime);
        }
        // 接著可以調(diào)用包裝的實際任務(wù)的run方法
        try {
            task.run();
        } catch (Exception e) {
            log.error("run task error", e);
            throw e;
        }
        // 任務(wù)運行完畢以后,會設(shè)置任務(wù)運行結(jié)束的時間
        this.endTime = System.currentTimeMillis();
        // 此處可以通過調(diào)用監(jiān)控系統(tǒng)的API,實現(xiàn)監(jiān)控指標(biāo)上報
        // 用線程任務(wù)的endTime - startTime,其實就是任務(wù)運行時間
        // 這邊打印任務(wù)執(zhí)行時間,也可以輸出到監(jiān)控系統(tǒng)中
        if(endTime - startTime > durMs) {
            log.info("任務(wù)信息: [{}], 任務(wù)執(zhí)行時間: [{}]ms", taskInfo, endTime - startTime);
        }
    }
}

使用:

我們還可以在包裝類里面封裝各種監(jiān)控行為,如本例打印日志執(zhí)行時間等。

原理探究

那大家有沒有想過為什么任務(wù)出錯會導(dǎo)致異常無法打印,甚至調(diào)度都取消了呢?讓我們從源碼出發(fā),一探究竟。

下面是調(diào)度任務(wù)的入口方法

// ScheduledThreadPoolExecutor#scheduleAtFixedRate
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0)
        throw new IllegalArgumentException();
    // 將執(zhí)行任務(wù)和參數(shù)包裝成ScheduledFutureTask對象
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period));
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    // 延遲執(zhí)行
    delayedExecute(t);
    return t;
}
復(fù)制代碼

這個方法主要做了兩個事情

  • 將執(zhí)行任務(wù)和參數(shù)包裝成ScheduledFutureTask對象
  • 調(diào)用delayedExecute方法延遲執(zhí)行任務(wù)

延遲或周期性任務(wù)的主要執(zhí)行方法, 主要是將任務(wù)丟到隊列中,后續(xù)由工作線程獲取執(zhí)行。

// ScheduledThreadPoolExecutor#delayedExecute
private void delayedExecute(RunnableScheduledFuture<?> task) {
        if (isShutdown())
            reject(task);
        else {
            // 將任務(wù)丟到阻塞隊列中
            super.getQueue().add(task);
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                // 開啟工作線程,去執(zhí)行任務(wù),或者從隊列中獲取任務(wù)執(zhí)行
                ensurePrestart();
        }
    }

現(xiàn)在任務(wù)已經(jīng)在隊列中了,我們看下任務(wù)執(zhí)行的內(nèi)容是什么,還記得前面的包裝對象 ScheduledFutureTask 類,它的實現(xiàn)類是 ScheduledFutureTask ,繼承了Runnable類。

// ScheduledFutureTask#run方法
public void run() {
    // 是不是周期性任務(wù)
    boolean periodic = isPeriodic();
    if (!canRunInCurrentRunState(periodic))
        cancel(false);
    // 不是周期性任務(wù)的話, 直接調(diào)用一次下面的run    
    else if (!periodic)
        ScheduledFutureTask.super.run();
    // 如果是周期性任務(wù),則調(diào)用runAndReset方法,如果返回true,繼續(xù)執(zhí)行
    else if (ScheduledFutureTask.super.runAndReset()) {
        // 設(shè)置下次調(diào)度時間
        setNextRunTime();
        // 重新執(zhí)行調(diào)度任務(wù)
        reExecutePeriodic(outerTask);
    }
}

這里的關(guān)鍵就是看 ScheduledFutureTask.super.runAndReset() 方法是否返回true,如果是true的話繼續(xù)調(diào)度。

runAndReset方法也很簡單,關(guān)鍵就是看報異常如何處理。

// FutureTask#runAndReset
protected boolean runAndReset() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return false;
    // 是否繼續(xù)下次調(diào)度,默認(rèn)false
    boolean ran = false;
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                // 執(zhí)行任務(wù)
                c.call(); 
                // 執(zhí)行成功的話,設(shè)置為true
                ran = true;
                // 異常處理,關(guān)鍵點
            } catch (Throwable ex) {
                // 不會修改ran的值,最終是false,同時也不打印異常堆棧
                setException(ex);
            }
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    // 返回結(jié)果
    return ran && s == NEW;
}
  • 關(guān)鍵點ran變量,最終返回是不是下次繼續(xù)調(diào)度執(zhí)行
  • 如果拋出異常的話,可以看到不會修改ran為true。

總結(jié)

Java的ScheduledThreadPoolExecutor定時任務(wù)線程池所調(diào)度的任務(wù)中如果拋出了異常,并且異常沒有捕獲直接拋到框架中,會導(dǎo)致ScheduledThreadPoolExecutor定時任務(wù)不調(diào)度了。

這個結(jié)論希望大家一定要記住,不然非??樱P(guān)鍵是有時候測試環(huán)境、開發(fā)環(huán)境還無法復(fù)現(xiàn),有一定的隨機性,真的到了生產(chǎn)就完蛋了。

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

關(guān)于這些知識點,我們不僅要知其然,還要知其所以然,這樣才會記憶深刻,不然很容易遺忘。

相關(guān)文章

  • 詳解Java中JSON數(shù)據(jù)的生成與解析

    詳解Java中JSON數(shù)據(jù)的生成與解析

    今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Java中JSON數(shù)據(jù)的生成與解析展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • SpringBoot中整合JodConverter實現(xiàn)文件在線預(yù)覽功能

    SpringBoot中整合JodConverter實現(xiàn)文件在線預(yù)覽功能

    Spring Boot JodConverter是一個基于Spring Boot框架的文檔轉(zhuǎn)換工具,它使用JodConverter庫來實現(xiàn)文檔格式之間的轉(zhuǎn)換,本文主要介紹了SpringBoot中整合JodConverter實現(xiàn)文件在線預(yù)覽功能,需要的朋友可以參考下
    2024-04-04
  • Redisson分布式鎖的原理及分析

    Redisson分布式鎖的原理及分析

    分布式鎖的關(guān)鍵問題包括續(xù)期、釋放、可重入和互斥,Redisson提供完善的解決方案,通過WatchDog機制自動續(xù)期,支持分布式鎖的重入和高效釋放,避免死鎖,提升資源利用率
    2026-05-05
  • Java Reference源碼解析

    Java Reference源碼解析

    這篇文章主要為大家詳細(xì)解析了Java Reference源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • SpringBoot實現(xiàn)對超大文件進行異步壓縮下載的使用示例

    SpringBoot實現(xiàn)對超大文件進行異步壓縮下載的使用示例

    在Web應(yīng)用中,文件下載功能是一個常見的需求,本文介紹了SpringBoot實現(xiàn)對超大文件進行異步壓縮下載的使用示例,具有一定的參考價值,感興趣的可以了解一下,
    2023-09-09
  • 使用Spring?Boot+gRPC構(gòu)建微服務(wù)并部署的案例詳解

    使用Spring?Boot+gRPC構(gòu)建微服務(wù)并部署的案例詳解

    這篇文章主要介紹了使用Spring?Boot+gRPC構(gòu)建微服務(wù)并部署,Spring Cloud僅僅是一個開發(fā)框架,沒有實現(xiàn)微服務(wù)所必須的服務(wù)調(diào)度、資源分配等功能,這些需求要借助Kubernetes等平臺來完成,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-06-06
  • java實現(xiàn)查找替換功能

    java實現(xiàn)查找替換功能

    這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)查找替換功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • java身份證合法性校驗并提取身份證有效信息

    java身份證合法性校驗并提取身份證有效信息

    這篇文章主要為大家詳細(xì)介紹了java身份證合法性校驗,并獲取并根據(jù)身份證號提取身份證相關(guān)信息,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • 使用springboot每日推送早安問候語到用戶微信的全過程

    使用springboot每日推送早安問候語到用戶微信的全過程

    近期網(wǎng)上又出現(xiàn)一股給女朋友做微信公眾號推送的潮流,所以這篇文章主要給大家介紹了關(guān)于如何使用springboot每日推送早安問候語到用戶微信的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-11-11
  • Windows下apache ant安裝、環(huán)境變量配置教程

    Windows下apache ant安裝、環(huán)境變量配置教程

    這篇文章主要介紹了Windows下apache ant安裝、環(huán)境變量配置教程,ANT的安裝很簡單,本文同時講解了驗證安裝是否成功的方法和使用方法實例,需要的朋友可以參考下
    2015-06-06

最新評論

安西县| 綦江县| 柳河县| 石狮市| 昭平县| 咸宁市| 黄大仙区| 大兴区| 平远县| 英山县| 嘉定区| 安顺市| 连城县| 长子县| 长寿区| 丰台区| 庆阳市| 南通市| 毕节市| 闵行区| 钟祥市| 都江堰市| 灵璧县| 聂拉木县| 吉安县| 神木县| 肇源县| 宜城市| 大冶市| 江川县| 崇礼县| 临海市| 西林县| 桂平市| 通城县| 惠水县| 安康市| 从江县| 康定县| 江永县| 抚州市|