当前位置:主页 > java教程 > ScheduledThreadPoolExecutor坑

ScheduledThreadPoolExecutor巨坑解决

发布:2023-03-27 15:55:01 59


我们帮大家精选了相关的编程文章,网友勾伟泽根据主题投稿了本篇教程内容,涉及到ScheduledThreadPoolExecutor、坑、ScheduledThreadPoolExecutor坑解决、ScheduledThreadPoolExecutor坑相关内容,已被791网友关注,相关难点技巧可以阅读下方的电子资料。

ScheduledThreadPoolExecutor坑

概述

最近在做一些优化的时候用到了ScheduledThreadPoolExecutor。

虽然知道这个玩意,但是也很久没用,本着再了解了解的心态,到网上搜索了一下,结果就发现网上有些博客在说ScheduledThreadPoolExecutor有巨坑!!!

瞬间,我的兴趣就被激起来了,马上进去学习了一波~

不看不知道,看完后马上把我的代码坑给填上了~

下面就当记录一下吧,顺便也带大家了解了解,大家看完后也赶紧看看自己公司的项目代码有没有这种漏洞,有的话赶紧给填上,升级加薪指日可待!!!

坑是啥?

先看下面案例代码

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(() -> {
      // 模拟业务逻辑
      int num = atomicInteger.getAndIncrement();
      // 模拟出现异常
      if (num > 3) {
        throw new RuntimeException("定时任务执行异常");
      }
      System.out.println("别坑我!");
    }, 0, 1, TimeUnit.SECONDS);
    try {
      TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    scheduledThreadPoolExecutor.shutdown();
  }
}

案例代码逻辑很简单,主线程等待5秒后关闭线程池,定时任务执行三次后模拟抛出RuntimeException

但是我们看看执行结果,只执行了三次!

因为某种情况下,定时任务在执行第四次时出现异常,从而导致任务调度被取消,不会继续执行

而且,异常信息也没有对外抛出!

那么咋解决嘞?try-catch就行了呗~

可以看到执行结果,虽然执行异常,但是任务却还是一直在调度~

代码里使用工具类对Runnable任务包了一层,就是加了try-catch

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

okok,总结一下,坑就是: 任务如果抛出异常就不会继续调度执行了,赶紧去try-catch吧!!!

大家赶紧去看看自己代码有没有这个坑吧,本文到此结束!

开个玩笑~ 光知道有坑哪能不知道为啥坑,接下来就带大家了解一下坑到底是啥!

怎么坑的?

直接进入scheduleAtFixedRate源码查看

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,                   long initialDelay,                   long period,                   TimeUnit unit) {
    // 参数校验
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0L)
        throw new IllegalArgumentException();
    // 将任务、执行时间、周期等封装到ScheduledFutureTask内
    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;
    // 延时执行
    delayedExecute(t);
    return t;
}

因为我们提交的任务被封装在ScheduledFutureTask,所以我们直接来看ScheduledFutureTaskrun方法

public void run() {
  // 校验当前状态是否还能执行任务,不能执行直接cancel取消
  if (!canRunInCurrentRunState(this))
    cancel(false);
  else if (!isPeriodic())
    // 如果不是周期性的,直接调用父类run方法执行一次即可
    super.run();
  else if (super.runAndReset()) { // 周期性任务,调用runAndReset运行并重置
    // 设置下一次的执行时间
    setNextRunTime();
    // 将任务重新加入队列,进行调度
    reExecutePeriodic(outerTask);
  }
}
public boolean isPeriodic() {
  return period != 0;
}

我们是周期性任务,所以直接看runAndReset源码

protected boolean runAndReset() {
    // 检查任务状态,cas机制防止并发执行任务
    if (state != NEW ||
        !RUNNER.compareAndSet(this, null, Thread.currentThread()))
        return false;
    // 默认不周期执行任务
    boolean ran = false;
    // state为NEW状态
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                // 执行任务
                c.call();
                // 正常执行成功,设置为true代表周期执行
                ran = true;
            } catch (Throwable ex) {
                // 但是,如果执行异常!则不会将ran = true,所以最终返回false
                setException(ex);
            }
        }
    } finally {
        runner = null;
        // 设置为NEW状态
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    // 正常执行完之后,结果为true,能够周期执行
    // 但如果执行异常,ran为false,返回结果为false
    return ran && s == NEW;
}

通过上面源码,我们可以很清楚的了解到,就是因为任务执行异常,且没有被try-catch,所以导致任务没有被再次加入到队列中进行调度。

并且通过文章开头,我们还能看到任务执行异常,但是却没有抛出异常信息

那是因为异常被封装了,只有调用get方法时,才会抛出异常

/** 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既可以为任务执行结果也可以为异常信息
        outcome = t;
        // 将state设置为异常状态,state=3
        STATE.setRelease(this, EXCEPTIONAL); // final state
        finishCompletion();
    }
}
// 调用get方法阻塞获取结果
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();
  // 所以会走到这里,对外抛出了任务执行的异常
  throw new ExecutionException((Throwable)x);
}

总结

通过上面对源码的了解,我们了解到,如果周期性任务执行出现异常,并且没有被try-catch,会导致该周期性任务不会再被放入到队列中进行调度执行。

以上就是ScheduledThreadPoolExecutor巨坑解决的详细内容,更多关于ScheduledThreadPoolExecutor坑的资料请关注码农之家其它相关文章!


参考资料

相关文章

  • mybatis if传入字符串数字踩坑记录及解决

    发布:2023-04-11

    这篇文章主要介绍了mybatis if传入字符串数字踩坑记录及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教


  • 解析怎么玩MySQL 5.7临时表空间不掉坑

    发布:2020-01-16

    这篇文章主要给大家介绍了关于MySQL 5.7临时表空间如何玩才能不掉坑里的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用mysql具有一定的参考学习价值,需要的朋友们下面随


  • Win10中python3.7安装dlib模块的技巧和坑

    发布:2020-01-30

    这篇文章主要介绍了Win10环境python3.7安装dlib模块趟过的坑,本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下


  • Spring RestTemplate的使用与踩坑

    发布:2023-04-05

    RestTemplate是一个执行HTTP请求的同步阻塞式工具类,它仅仅只是在HTTP客户端库基础上,封装了更加简单易用的模板方法API,方便程序员利用已提供的模板方法发起网络请求和处理,能很大程度上提升我们的开发效率


  • 解决一个pandas执行模糊查询sql的坑

    发布:2023-01-09

    给大家整理一篇关于python的教程,这篇文章主要介绍了解决一个pandas执行模糊查询sql的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧


  • Mac M1安装JDK的实战避坑指南

    发布:2023-04-08

    这篇文章主要给大家介绍了关于Mac M1安装JDK避坑的相关资料,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下


  • mybatis-plus 关于savebatch,saveorupdatebatch遇到的坑及解决办法

    发布:2023-03-11

    本文主要介绍了mybatis-plus 关于savebatch,saveorupdatebatch遇到的坑及解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧


  • 总结vue填坑之解决部分浏览器不支持pushState方法

    发布:2020-03-11

    这篇文章主要介绍了详解vue填坑之解决部分浏览器不支持pushState方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧


网友讨论