溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ScheduledThreadPoolExecutor的坑如何解決

發(fā)布時間:2023-02-22 14:24:53 來源:億速云 閱讀:104 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下ScheduledThreadPoolExecutor的坑如何解決的相關(guān)知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

先看下面案例代碼

public class ScheduledThreadPoolExecutorTest {
  public static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
  public static AtomicInteger atomicInteger = new AtomicInteger(1);
  public static void main(String[] args) {
    scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
      // 模擬業(yè)務邏輯
      int num = atomicInteger.getAndIncrement();
      // 模擬出現(xiàn)異常
      if (num > 3) {
        throw new RuntimeException("定時任務執(zhí)行異常");
      }
      System.out.println("別坑我!");
    }, 0, 1, TimeUnit.SECONDS);
    try {
      TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    scheduledThreadPoolExecutor.shutdown();
  }
}

案例代碼邏輯很簡單,主線程等待5秒后關(guān)閉線程池,定時任務執(zhí)行三次后模擬拋出RuntimeException

但是我們看看執(zhí)行結(jié)果,只執(zhí)行了三次!

因為某種情況下,定時任務在執(zhí)行第四次時出現(xiàn)異常,從而導致任務調(diào)度被取消,不會繼續(xù)執(zhí)行

而且,異常信息也沒有對外拋出!

ScheduledThreadPoolExecutor的坑如何解決

那么咋解決嘞?try-catch就行了唄~

ScheduledThreadPoolExecutor的坑如何解決

可以看到執(zhí)行結(jié)果,雖然執(zhí)行異常,但是任務卻還是一直在調(diào)度~

代碼里使用工具類對Runnable任務包了一層,就是加了try-catch

public class RunnableDecoratorUtil {
   public static Runnable runnableDecorator(Runnable runnable) {
      return () -> {
         try {
            runnable.run();
         } catch (Exception e) {
            e.printStackTrace();
         }
      };
   }
}

怎么坑的?

直接進入scheduleAtFixedRate源碼查看

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit) {
    // 參數(shù)校驗
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0L)
        throw new IllegalArgumentException();
    // 將任務、執(zhí)行時間、周期等封裝到ScheduledFutureTask內(nèi)
    ScheduledFutureTask<Void> sft =
        new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period),
                                      sequencer.getAndIncrement());
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    // 延時執(zhí)行
    delayedExecute(t);
    return t;
}

因為我們提交的任務被封裝在ScheduledFutureTask,所以我們直接來看ScheduledFutureTaskrun方法

public void run() {
  // 校驗當前狀態(tài)是否還能執(zhí)行任務,不能執(zhí)行直接cancel取消
  if (!canRunInCurrentRunState(this))
    cancel(false);
  else if (!isPeriodic())
    // 如果不是周期性的,直接調(diào)用父類run方法執(zhí)行一次即可
    super.run();
  else if (super.runAndReset()) { // 周期性任務,調(diào)用runAndReset運行并重置
    // 設置下一次的執(zhí)行時間
    setNextRunTime();
    // 將任務重新加入隊列,進行調(diào)度
    reExecutePeriodic(outerTask);
  }
}
public boolean isPeriodic() {
  return period != 0;
}

我們是周期性任務,所以直接看runAndReset源碼

protected boolean runAndReset() {
    // 檢查任務狀態(tài),cas機制防止并發(fā)執(zhí)行任務
    if (state != NEW ||
        !RUNNER.compareAndSet(this, null, Thread.currentThread()))
        return false;
    // 默認不周期執(zhí)行任務
    boolean ran = false;
    // state為NEW狀態(tài)
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                // 執(zhí)行任務
                c.call();
                // 正常執(zhí)行成功,設置為true代表周期執(zhí)行
                ran = true;
            } catch (Throwable ex) {
                // 但是,如果執(zhí)行異常!則不會將ran = true,所以最終返回false
                setException(ex);
            }
        }
    } finally {
        runner = null;
        // 設置為NEW狀態(tài)
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    // 正常執(zhí)行完之后,結(jié)果為true,能夠周期執(zhí)行
    // 但如果執(zhí)行異常,ran為false,返回結(jié)果為false
    return ran && s == NEW;
}

通過上面源碼,我們可以很清楚的了解到,就是因為任務執(zhí)行異常,且沒有被try-catch,所以導致任務沒有被再次加入到隊列中進行調(diào)度。

并且通過文章開頭,我們還能看到任務執(zhí)行異常,但是卻沒有拋出異常信息

那是因為異常被封裝了,只有調(diào)用get方法時,才會拋出異常

ScheduledThreadPoolExecutor的坑如何解決

/** The result to return or exception to throw from get() */
private Object outcome;
private volatile int state;
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
protected void setException(Throwable t) {
    if (STATE.compareAndSet(this, NEW, COMPLETING)) {
        // 將異常信息賦值給outcome
       // outcome既可以為任務執(zhí)行結(jié)果也可以為異常信息
        outcome = t;
        // 將state設置為異常狀態(tài),state=3
        STATE.setRelease(this, EXCEPTIONAL); // final state
        finishCompletion();
    }
}
// 調(diào)用get方法阻塞獲取結(jié)果
public V get() throws InterruptedException, ExecutionException {
  int s = state;
  if (s <= COMPLETING)
    s = awaitDone(false, 0L);
  return report(s);
}
private V report(int s) throws ExecutionException {
  Object x = outcome;
  // 此時s = EXCEPTIONAL = 3
  if (s == NORMAL)
    return (V)x;
  if (s >= CANCELLED)
    throw new CancellationException();
  // 所以會走到這里,對外拋出了任務執(zhí)行的異常
  throw new ExecutionException((Throwable)x);
}

以上就是“ScheduledThreadPoolExecutor的坑如何解決”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI