ScheduledThreadPoolExecutor有坑嗷~

2023-10-31

概述

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

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

image-20230218230005780

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

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

image-20230218230252296

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

image-20230218230436063


坑是啥?

先看下面案例代码

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

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

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

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

image-20230218230546131

image-20230218225134884

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

image-20230218231957359

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

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

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

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

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

image-20230218230853339

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


怎么坑的?

直接进入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方法时,才会抛出异常

image-20230219142945635

/** 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,会导致该周期性任务不会再被放入到队列中进行调度执行。


结尾

我是 Code皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步!

觉得文章不错的话,可以关注我,或者是我的公众号——JavaCodes

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ScheduledThreadPoolExecutor有坑嗷~ 的相关文章

  • 从文件中读取文本并将每行中的每个单词存储到单独的变量中

    我有一个包含以下内容的 txt 文件 1 1111 47 2 2222 92 3 3333 81 我想逐行读取并将每个单词存储到不同的变量中 例如 当我读取第一行 1 1111 47 时 我想将第一个单词 1 存储到var 1 1111 进
  • 使用 Firebase Java API 检索/格式化数据的最佳方式

    我在用着Firebase用于数据存储Android项目 并使用Firebase Java API来处理数据 不过 我不确定我是否尽可能高效地完成此操作 并且我希望获得一些有关检索和格式化数据的最佳实践的建议 我的Firebase存储库看起来
  • CustomTaskChange 在调用 updateSQL 时实际执行

    我有一个CustomTaskChange在 Liquibase 中 除了其他变更集 我希望我的应用程序在实际执行之前显示所有 ChangeSet 的 SQL 以我的理解 updateSQL应该预览 SQL 并且不执行任何操作 ACustom
  • Java 密钥库 - 以编程方式从密钥库文件中选择要使用的证书

    我有一个 java 密钥库文件 其中包含多个客户端证书 我希望在 Java 应用程序中仅选择其中一个证书来连接到服务 有没有一种简单的方法可以做到这一点 到目前为止 我找到解决方案的唯一方法是使用原始密钥库文件中的客户端证书详细信息 通过其
  • Vaadin框架播放视频

    我可以使用 Vaadin Framewotk 播放视频吗 主要思想是从本地驱动器加载 flv 或 avi 格式的视频文件 并使用 vaadin 框架在网络上播放 谢谢 Sampler中有一个示例 http demo vaadin com s
  • 是否有适用于 Java 的 Harel Statechart DSL 工具?

    我正在寻找一种能够理解 DSL 的工具 在其中我可以定义生成 Java 代码的状态图 或者 DSL 中的状态图可以按原样运行 该工具最好用 Java 编写 并且必须根据 Harel 状态图 或等效的 UML 2 状态机 的定义支持超级状态和
  • 对 Java Servlet 进行单元测试

    我想知道对 servlet 进行单元测试的最佳方法是什么 只要内部方法不引用 servlet 上下文 测试内部方法就不是问题 但是测试 doGet doPost 方法以及引用上下文或使用会话参数的内部方法呢 有没有办法简单地使用经典工具 例
  • 在java中迭代日期

    我需要遍历一系列日期 不确定如何在 for 循环中获取第二天 我在用java util Date So plusDays 1 不能在 for 循环中用于获取下一个日期 Used date1 new Date date1 getTime 10
  • 如何统计lucene索引中每个文档的term数?

    我想知道 lucene 索引中每个文档的术语数量 我一直在 API 和互联网上搜索 但没有结果 你能帮助我吗 Lucene 的构建是为了回答相反的问题 即哪些文档包含给定术语 因此 为了获取文档的术语数量 您必须进行一些修改 第一种方法是存
  • java - IBM-IEEE 双精度浮点字节转换

    我需要在 Java 中对字节数组进行 IBM IEEE 浮点转换 我能够使用成功地进行单精度浮点字节的转换http www thecodingforums com threads c code for converting ibm 370
  • javax.validation 的 @AssertTrue - 它不应该创建错误消息吗?

    我在 Spring MVC 命令 bean 中有以下代码 AssertTrue public boolean isConditionTrue return false private boolean conditionTrue 我的 JSP
  • .NET 中的 Class.forName() 等效项?

    动态获取对象类型然后创建它的新实例的 C 方法是什么 例如 如何在 C 中实现以下 Java 代码的结果 MyClass x MyClass Class forName classes MyChildClass newInstance Lo
  • android 中的 lang.NumberFormatException

    我有以下代码 除了在后台线程中从数据库读取一些值并使用这些值之外什么也不做 我使用 jar 绘制折线图 对于我用于每个数组值的折线图 问题是第三个我传递给绘制 LineChart 的构造函数的参数是 float float viteza S
  • Eclipse 错误:“设置构建路径”遇到错误

    我正在使用一个名为 jtwitter 的 API 它有一个 jar 文件 jtwitter jar 我一直在使用它并使用 git 维护它 我把代码托管在github上 有些天 我没有碰过它的代码 今天 当我克隆我的 git repo 时 实
  • 如何使用二叉树中的递归来完成回溯

    我正在尝试插入一个二进制节点 我的代码很复杂 没有希望挽救它 所以我计划重写它 基本上我没有考虑回溯 也没有仔细考虑算法 我正在尝试使用顺序遍历插入二进制节点 但我不明白应该如何回溯 D B E A C F 我如何搜索根 D 的左子树 然后
  • 参数列表中的“...”是什么意思? doInBackground(字符串...参数)

    我不明白那个语法 尝试用谷歌搜索各种单词加上 是没有用的 它被称为varargs http java sun com j2se 1 5 0 docs guide language varargs html 这个事实应该产生更好的谷歌结果 h
  • Guice 字段注入不起作用(返回 null)

    我在使用 Guice 时遇到空值问题 接下来我将向您展示一个类似场景的示例 我知道字段注入是一种不好的做法 但我希望它在演示中像这样工作 我有一个名为B 这是我要注入的 class B Inject public B public void
  • ClassNotFoundException:在嵌入了 cxf 依赖项的 OSGi 包中找不到 org.glassfish.jersey.internal.RuntimeDelegateImpl

    这与jax rs 2 0 更改默认实现 https stackoverflow com questions 17366266 jax rs 2 0 change default implementation我有一个 OSGi 包 其中包含
  • Tomcat 与 Weblogic JNDI 查找

    我们使用的 Weblogic 服务器已配置为允许 JNDI 数据源名称 例如 appds 对于开发 本地主机 我们可能会运行 Tomcat 并且在 server xml 的 部分中声明时 Tomcat 会将 JNDI 数据源挂在 JNDI
  • 如何为信号量中等待的线程提供优先级?

    我使用信号量来限制访问函数的线程数量 我希望接下来要唤醒的线程应该由我将给出的某个优先级选择 而不是默认信号量唤醒它们的方式 我们怎样才能做到这一点 这是实现 class MyMathUtil2 implements Runnable do

随机推荐