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坑的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解Java ScheduledThreadPoolExecutor的踩坑与解决方法

    目录 概述 还原"大坑" 解决方案 更推荐的做法 原理探究 总结 概述 最近项目上反馈某个重要的定时任务突然不执行了,很头疼,开发环境和测试环境都没有出现过这个问题.定时任务采用的是ScheduledThreadPoolExecutor,后来一看代码发现踩了一个大坑.... 还原"大坑" 这个坑就是如果ScheduledThreadPoolExecutor中执行的任务出错抛出异常后,不仅不会打印异常堆栈信息,同时还会取消后面的调度, 直接看例子. @Test pub

  • java 定时器线程池(ScheduledThreadPoolExecutor)的实现

    前言 定时器线程池提供了定时执行任务的能力,即可以延迟执行,可以周期性执行.但定时器线程池也还是线程池,最底层实现还是ThreadPoolExecutor,可以参考我的另外一篇文章多线程–精通ThreadPoolExecutor. 特点说明 1.构造函数 public ScheduledThreadPoolExecutor(int corePoolSize) { // 对于其他几个参数在ThreadPoolExecutor中都已经详细分析过了,所以这里,将不再展开 // 这里我们可以看到调用基类

  • java高并发ScheduledThreadPoolExecutor与Timer区别

    目录 正文 二者的区别 线程角度 系统时间敏感度 是否捕获异常 任务是否具备优先级 是否支持对任务排序 能否获取返回的结果 二者简单的示例 Timer类简单示例 ScheduledThreadPoolExecutor类简单示例 正文 JDK 1.5开始提供ScheduledThreadPoolExecutor类,ScheduledThreadPoolExecutor类继承ThreadPoolExecutor类重用线程池实现了任务的周期性调度功能.在JDK 1.5之前,实现任务的周期性调度主要使用

  • Java自带定时任务ScheduledThreadPoolExecutor实现定时器和延时加载功能

    java.util.concurrent.ScheduledThreadPoolExecutor 是JDK1 .6之后自带的包,功能强大,能实现定时器和延时加载的功能 各类功能和处理方面优于Timer 1.定时器: ScheduledThreadPoolExecutor  有个scheduleAtFixedRate(command, initialDelay, period, unit) ;方法 command: 执行的线程(可自己New一个) initialDelay:初始化执行的延时时间 p

  • ScheduledThreadPoolExecutor巨坑解决

    目录 概述 坑是啥? 怎么坑的? 总结 概述 最近在做一些优化的时候用到了ScheduledThreadPoolExecutor. 虽然知道这个玩意,但是也很久没用,本着再了解了解的心态,到网上搜索了一下,结果就发现网上有些博客在说ScheduledThreadPoolExecutor有巨坑!!! 瞬间,我的兴趣就被激起来了,马上进去学习了一波- 不看不知道,看完后马上把我的代码坑给填上了- 下面就当记录一下吧,顺便也带大家了解了解,大家看完后也赶紧看看自己公司的项目代码有没有这种漏洞,有的话赶

  • mybatis.type-aliases-package之巨坑的解决

    mybatis.type-aliases-package之巨坑 mapper.xml中的resultType中经常会用到一些自定义POJO,你可以用完全限定名来指定这些POJO的引用 例如: <select id="getUsers" resultType="com.majing.learning.mybatis.entity.User">, 又或者你可以通过在application.properties中指定POJO扫描包来让mybatis自动扫描到自

  • Laravel5.2使用Captcha生成验证码实现登录(session巨坑)

    最近有朋友要我帮忙弄一下laravel的验证码登陆,所以稍稍研究了一下.(本人都快忘了咋使用laravel了) 首先,安装laravel就不用在下赘述了吧,我的版本是5.2.45(注:laravel5.2.6以上的版本中间件可以自动加载),这还是挺重要的. 安装完成之后,你需要使用composer来加载你的Captcha,具体方法就是在你的composer.json中的require数组中加上"gregwar/captcha":"1.*"这行代码.然后嘞,就在你的项

  • 关于在LayUI中使用AJAX提交巨坑记录

    如下所示: <script> layui.use(['layer', 'form','laydate'], function(){ var layer = layui.layer ,laydate=layui.laydate ,form = layui.form; form.on('submit(go)', function(data){ $.ajax({ url:'/user/addOrUpdate', method:'post', data:data.field, dataType:'JS

  • 微信小程序调用wx.getImageInfo遇到的坑解决

    这几天做到微信小程序详情页分享的功能,需要把原页面的一些参数带到分享页,然后在分享页需要获取图片的宽高等基本信息. 1.先说分享传参的方式: 在onShareAppMessage方法里面返回的path里面可以带参数传过去,具体传参的方式有两种,一种是可以传对象(需要把JSON对象stringiny),另外一种是通过一般的参数拼接的方式一个个拼. 代码: onShareAppMessage: function (res) { let data = this.data; let shareParam

  • Keras构建神经网络踩坑(解决model.predict预测值全为0.0的问题)

    终于构建出了第一个神经网络,Keras真的很方便. 之前不知道Keras这么方便,在构建神经网络的过程中绕了很多弯路,最开始学的TensorFlow,后来才知道Keras. TensorFlow和Keras的关系,就像c语言和python的关系,所以Keras是真的好用. 搞不清楚数据的标准化和归一化的关系,想对原始数据做归一化,却误把数据做了标准化,导致用model.predict预测出来的值全是0.0,在网上搜了好久但是没搜到答案,后来自己又把程序读了一遍,突然灵光一现好像是数据归一化出了问

  • JSON.stringify实现深拷贝的巨坑详解

    目录 当对象中有时间类型的元素时候-----时间类型会被变成字符串类型数据 当对象中有undefined类型或function类型的数据时 --- undefined和function会直接丢失 当对象中有NaN.Infinity和-Infinity这三种值的时候 --- 会变成null 当对象循环引用的时候 --会报错 总结 当对象中有时间类型的元素时候-----时间类型会被变成字符串类型数据 const obj = { date:new Date() } typeof obj.date ==

  • ShardingJdbc读写分离的BUG踩坑解决

    目录 前言 数据库介绍 1. 常规写完读 2. 在一个 service 里面调用另一个 service 3. 新开一个线程去调用 service2 4. service2 新开一个事务执行 前言 最近公司准备接入ShardingJdbc做读写分离了,老大让我们理一理有没有写完数据立马读的场景,因为主从同步是有延迟的,如果写完读取数据走到从库,而从库正好有延迟,没读取到数据,岂不是造成了生产事故. 今天我们来看看,ShardingJdbc作为一个成熟的框架是怎么处理写完数据立即读取的场景的. 数据

  • Python RawString与open文件的newline换行符遇坑解决

    目录 背景 思路 遇到的问题 思考过程 Raw String 如果字符串没转义字符,那么 Raw String 跟普通 String 完全一致 误区:注意单个字符的引号问题 启发 正则替换的问题 open 文件的 newline 参数 背景 一次工作中,我需要完成某个文件的字符串替换. 需求是这样的:文件A有个占位符,需要利用Python3,把占位符替换成文件B的内容.文件都不大,可以一次性读到内存处理. 我想,这不是简单的open read replace write就搞定了嘛? 结果,还真有

  • iOS schem与Universal Link 调试时踩坑解决记录

    目录 简介 AppDelegate和SceneDelegate 问题:在iOS13以上冷启动的时候不会走代理函数! 如果你用了Scheme方式: iOS13之前会走这个代理函数 iOS13之后会走 如果你用了Universal Link方式: iOS13之前会走这个代理函数 iOS13之后会走 总结 简介 scheme和Universal Link是在iOS中两种可以在网页中点击回跳到自己预定的APP的两种方式.至于这两种方式需要怎么配置,这里就不做详细的介绍了.网上的文章一搜一大堆.今天主要是

随机推荐