1. 程式人生 > 實用技巧 >幾種定時任務(Timer、TimerTask、ScheduledFuture)的退出—結合真實案例【JAVA】

幾種定時任務(Timer、TimerTask、ScheduledFuture)的退出—結合真實案例【JAVA】

工作中常常會有定時任務的開發需求,特別是移動端。最近筆者正好有所涉及,鑑於此,結合開發中的案例說明一下幾種定時任務的退出。

需求說明:定時更新正在生成的檔案大小和狀態【進行中、失敗、完成】,如果檔案生成完成,則退出【CoderBaby

排程這可以用Timer【可以呼叫schedule()或者scheduleAtFixedRate()方法實現】或者ScheduledExecutorService 【結合工作中其它的需求,筆者選用此】

ScheduledExecutorService的初始化(執行緒池):

private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
  • 自己手動實現——最樸素的方案【通過sleep來控制時間間隔、break來退出】
            scheduledExecutorService.execute(() -> {
                long oldCurFileSize = 0;
                while(true) {
                    try {
                        Thread.sleep(updateInternal * 1000);
                        long curFileSize = Files.size(Paths.get(TMP_PCAP_PATH + tmpPcapFileName));
                        
int status = getStatus(tmpPcapFileName); if (isFailed(status) || hasNoData(status) || isFinished(status)) { break; } if (curFileSize != oldCurFileSize) { updateFileInfo(tmpPcapFileName, curFileSize,
0); oldCurFileSize = curFileSize; } else { updateFileInfo(tmpPcapFileName, curFileSize, 3); // 延遲1秒,才能成功更新 Thread.sleep(1000); break; } } catch (Exception e) { logger.warn("Catch exception : " + e.toString()); } } });

注:

updateFileInfo—更新資料庫相關記錄;

getStatus查詢資料庫當前記錄的狀態,判定是否完成或者出現錯誤;

updateInternal控制定時任務的執行時間間隔(單位為秒)

  • TimerTask【通過cancel來退出】

定義一個內部類繼承TimerTask抽象類

    class ScheduledUpdateTrafficForensics extends TimerTask {
        private String tmpPcapFileName;
        private long oldCurrentFileSize = 0;

        public ScheduledUpdateTrafficForensics(String tmpPcapFileName) {
            this.tmpPcapFileName = tmpPcapFileName;
        }

        public void run() {
            try {
                long currentFileSize = Files.size(Paths.get(TMP_PCAP_PATH + tmpPcapFileName));
                int status = getStatus(tmpPcapFileName);
                if (isFailed(status) || hasNoData(status) || isFinished(status)) {
                    this.cancel();
                }
                if (oldCurrentFileSize != currentFileSize) {
                    updateFileInfo(tmpPcapFileName, currentFileSize, 0);
                } else {
                    updateFileInfo(tmpPcapFileName, currentFileSize, 3);
                    this.cancel();
                }
            } catch (IOException e) {
                logger.warn("Catch exception : " + e.toString());
            }
        }
    }

通過scheduleAtFixedRate介面來呼叫(設定時間間隔和且第一次執行的延遲時間)

            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(new ScheduledUpdateTrafficForensics(tmpPcapFileName),
                    updateInternal, pcapDownloadStatusUpdateInternal, TimeUnit.SECONDS);
  • ScheduledFuture【通過cancle來退出】

定義一個內部類繼承Runnable介面

     class ScheduledUpdateTrafficForensics implements Runnable {
        private String tmpPcapFileName;
        private long oldCurrentFileSize = 0;

        public ScheduledUpdateTrafficForensics(String tmpPcapFileName) {
            this.tmpPcapFileName = tmpPcapFileName;
        }

        public void run() {
            while (!scheduledFuture.isCancelled()) {
                try {
                    long currentFileSize = Files.size(Paths.get(TMP_PCAP_PATH + tmpPcapFileName));
                    int status = getStatus(tmpPcapFileName);
                    if (isFailed(status) || hasNoData(status)) {
                        scheduledFuture.cancel(true);
                        return;
                    }
                    if (!isFinished(status)) {
                        updateFileInfo(tmpPcapFileName, currentFileSize, 0);
                    }
                } catch (IOException e) {
                    logger.warn("Catch exception : " + e.toString());
                }
            }
        }
    }

通過scheduleAtFixedRate介面來呼叫(設定時間間隔和且第一次執行的延遲時間),並且將結果返回給ScheduledFuture

            scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(new ScheduledUpdateTrafficForensics(tmpPcapFileName),
                    updateInternal, pcapDownloadStatusUpdateInternal, TimeUnit.SECONDS);

注:通過scheduledFuture.cancel(true)後可能不能成功結束定時任務,所以必須通過手動呼叫isCancelled()來判斷是否被cancle(呼叫cancel後,再呼叫isCancelled() 【一定會返回true】)掉了,然後退出任務。相關原始碼註釋如下:

     * <p>After this method returns, subsequent calls to {@link #isDone} will
     * always return {@code true}.  Subsequent calls to {@link #isCancelled}
     * will always return {@code true} if this method returned {@code true}.

特別說明:

關於schedule(時間基準:執行的實際時間)和scheduleAtFixedRate(時間基準:理論時間點)的區別:

  • scheduleAtFixedRate排程一個task,在delay(ms)後開始排程,然後每經過period(ms)再次排程,貌似和方法—schedule是一樣的,其實不然。
  • schedule在計算下一次執行的時間的時候,是通過當前時間(在任務執行前得到) + 時間片,而scheduleAtFixedRate方法是通過當前需要執行的時間(也就是計算出現在應該執行的時間)+ 時間片,前者是執行的實際時間,而後者是理論時間點。

例如:schedule時間片是5s,那麼理論上會在5、10、15、20這些時間片被排程,但是如果由於某些CPU徵用導致未被排程,假如等到第8s才被第一次排程,那麼schedule方法計算出來的下一次時間應該是第13s而不是第10s,這樣有可能下次就越到20s後而被少排程一次或多次,而scheduleAtFixedRate方法就是每次理論計算出下一次需要排程的時間用以排序,若第8s被排程,那麼計算出應該是第10s,所以它距離當前時間是2s,那麼再排程佇列排序中,會被優先排程,那麼就儘量減少漏掉排程的情況。

詳情請移步https://www.cnblogs.com/dolphin0520/p/3938991.html

*********************************************************************************

精力有限,想法太多,專注做好一件事就行

  • 我只是一個程式猿。5年內把程式碼寫好,技術部落格字字推敲,堅持零拷貝和原創
  • 寫部落格的意義在於鍛鍊邏輯條理性,加深對知識的系統性理解,鍛鍊文筆,如果恰好又對別人有點幫助,那真是一件令人開心的事

*********************************************************************************