范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

ScheduledThreadPoolExecutor踩过最痛的坑

  概述
  最近项目上反馈某个重要的定时任务突然不执行了,很头疼,开发环境和测试环境都没有出现过这个问题。定时任务采用的是ScheduledThreadPoolExecutor,后来一看代码发现踩了一个大坑....还原"大坑"
  这个坑就是如果ScheduledThreadPoolExecutor中执行的任务出错抛出异常后,不仅不会打印异常堆栈信息,同时还会取消后面的调度, 直接看例子。@Test public void testException() throws InterruptedException {     // 创建1个线程的调度任务线程池     ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();     // 创建一个任务     Runnable runnable = new Runnable() {          volatile int num = 0;          @Override         public void run() {             num ++;             // 模拟执行报错             if(num > 5) {                 throw new RuntimeException("执行错误");             }             log.info("exec num: [{}].....", num);         }     };      // 每隔1秒钟执行一次任务     scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);     Thread.sleep(10000); }
  运行结果:
  只执行了5次后,就不打印,不执行了,因为报错了任务报错,也没有打印一次堆栈,更导致调度任务取消,后果十分严重。解决方案
  解决方法也非常简单,只要通过try catch捕获异常即可。
  运行结果:
  看到不仅打印了异常堆栈,而且也会进行周期性的调度。更推荐的做法
  更好的建议可以在自己的项目中封装一个包装类,要求所有的调度都提交通过我们统一的包装类, 如下代码:@Slf4j public class RunnableWrapper implements Runnable {     // 实际要执行的线程任务     private Runnable task;     // 线程任务被创建出来的时间     private long createTime;     // 线程任务被线程池运行的开始时间     private long startTime;     // 线程任务被线程池运行的结束时间     private long endTime;     // 线程信息     private String taskInfo;      private boolean showWaitLog;      /**      * 执行间隔时间多久,打印日志      */     private long durMs = 1000L;      // 当这个任务被创建出来的时候,就会设置他的创建时间     // 但是接下来有可能这个任务提交到线程池后,会进入线程池的队列排队     public RunnableWrapper(Runnable task, String taskInfo) {         this.task = task;         this.taskInfo = taskInfo;         this.createTime = System.currentTimeMillis();     }      public void setShowWaitLog(boolean showWaitLog) {         this.showWaitLog = showWaitLog;     }      public void setDurMs(long durMs) {         this.durMs = durMs;     }      // 当任务在线程池排队的时候,这个run方法是不会被运行的     // 但是当任务结束了排队,得到线程池运行机会的时候,这个方法会被调用     // 此时就可以设置线程任务的开始运行时间     @Override     public void run() {         this.startTime = System.currentTimeMillis();          // 此处可以通过调用监控系统的API,实现监控指标上报         // 用线程任务的startTime-createTime,其实就是任务排队时间         // 这边打印日志输出,也可以输出到监控系统中         if(showWaitLog) {             log.info("任务信息: [{}], 任务排队时间: [{}]ms", taskInfo, startTime - createTime);         }          // 接着可以调用包装的实际任务的run方法         try {             task.run();         } catch (Exception e) {             log.error("run task error", e);             throw e;         }          // 任务运行完毕以后,会设置任务运行结束的时间         this.endTime = System.currentTimeMillis();          // 此处可以通过调用监控系统的API,实现监控指标上报         // 用线程任务的endTime - startTime,其实就是任务运行时间         // 这边打印任务执行时间,也可以输出到监控系统中         if(endTime - startTime > durMs) {             log.info("任务信息: [{}], 任务执行时间: [{}]ms", taskInfo, endTime - startTime);         }      } }
  使用:
  我们还可以在包装类里面封装各种监控行为,如本例打印日志执行时间等。原理探究
  那大家有没有想过为什么任务出错会导致异常无法打印,甚至调度都取消了呢?让我们从源码出发,一探究竟。下面是调度任务的入口方法。// ScheduledThreadPoolExecutor#scheduleAtFixedRate public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,                                               long initialDelay,                                               long period,                                               TimeUnit unit) {     if (command == null || unit == null)         throw new NullPointerException();     if (period <= 0)         throw new IllegalArgumentException();     // 将执行任务和参数包装成ScheduledFutureTask对象     ScheduledFutureTask sft =         new ScheduledFutureTask(command,                                       null,                                       triggerTime(initialDelay, unit),                                       unit.toNanos(period));     RunnableScheduledFuture t = decorateTask(command, sft);     sft.outerTask = t;     // 延迟执行     delayedExecute(t);     return t; }
  这个方法主要做了两个事情:将执行任务和参数包装成ScheduledFutureTask对象调用delayedExecute方法延迟执行任务
  2.延迟或周期性任务的主要执行方法, 主要是将任务丢到队列中,后续由工作线程获取执行。// ScheduledThreadPoolExecutor#delayedExecute private void delayedExecute(RunnableScheduledFuture<?> task) {         if (isShutdown())             reject(task);         else {             // 将任务丢到阻塞队列中             super.getQueue().add(task);             if (isShutdown() &&                 !canRunInCurrentRunState(task.isPeriodic()) &&                 remove(task))                 task.cancel(false);             else                 // 开启工作线程,去执行任务,或者从队列中获取任务执行                 ensurePrestart();         }     }
  3.现在任务已经在队列中了,我们看下任务执行的内容是什么,还记得前面的包装对象ScheduledFutureTask类,它的实现类是ScheduledFutureTask,继承了Runnable类。// ScheduledFutureTask#run方法 public void run() {     // 是不是周期性任务     boolean periodic = isPeriodic();     if (!canRunInCurrentRunState(periodic))         cancel(false);     // 不是周期性任务的话, 直接调用一次下面的run         else if (!periodic)         ScheduledFutureTask.super.run();     // 如果是周期性任务,则调用runAndReset方法,如果返回true,继续执行     else if (ScheduledFutureTask.super.runAndReset()) {         // 设置下次调度时间         setNextRunTime();         // 重新执行调度任务         reExecutePeriodic(outerTask);     } }这里的关键就是看ScheduledFutureTask.super.runAndReset()方法是否返回true,如果是true的话继续调度。
  4.runAndReset方法也很简单,关键就是看报异常如何处理。// FutureTask#runAndReset protected boolean runAndReset() {     if (state != NEW ||         !UNSAFE.compareAndSwapObject(this, runnerOffset,                                      null, Thread.currentThread()))         return false;     // 是否继续下次调度,默认false     boolean ran = false;     int s = state;     try {         Callable c = callable;         if (c != null && s == NEW) {             try {                 // 执行任务                 c.call();                  // 执行成功的话,设置为true                 ran = true;                  // 异常处理,关键点             } catch (Throwable ex) {                 // 不会修改ran的值,最终是false,同时也不打印异常堆栈                 setException(ex);             }         }     } finally {         // runner must be non-null until state is settled to         // prevent concurrent calls to run()         runner = null;         // state must be re-read after nulling runner to prevent         // leaked interrupts         s = state;         if (s >= INTERRUPTING)             handlePossibleCancellationInterrupt(s);     }     // 返回结果     return ran && s == NEW; }关键点ran变量,最终返回是不是下次继续调度执行如果抛出异常的话,可以看到不会修改ran为true。总结
  Java的ScheduledThreadPoolExecutor定时任务线程池所调度的任务中如果抛出了异常,并且异常没有捕获直接抛到框架中,会导致ScheduledThreadPoolExecutor定时任务不调度了。这个结论希望大家一定要记住,不然非常坑,关键是有时候测试环境、开发环境还无法复现,有一定的随机性,真的到了生产就完蛋了。
  关于这些知识点,我们不仅要知其然,还要知其所以然,这样才会记忆深刻,不然很容易遗忘。

夏天就穿这40条长裤!凉快又显瘦,爽爆了hievbd!最近又是热shi人的一天!家和办公室都是天堂我这条命真的是空调给的!特别是配上半个西瓜这才是夏天正确的打开方式吧不过啊,夏天我还是很少穿短裤的因为一旦晒黑,就很难恢复苦等34年!元年后跟火焰红AJ3上脚图终于有了这两年对于喜欢OG的球鞋玩家来说简直太爽了。继大魔王AirJordan11季后赛AirJordan12相继回归后,近日元年火焰红AirJordan3确认今年正式发售。随着发售日期临预测状元,跌到第三顺位!火箭探花,打了5场夏季联赛,表现如何本届NBA选秀大会开启之前,小贾巴里史密斯在很长一段时间都被预测为状元。但令人意想不到的是,选秀夜当晚,魔术却临时变卦,第一顺位选择了班切罗。由于雷霆榜眼人选早已固定为霍姆格伦,导曼联转会操作签约3人花费7237万欧,离队8人入账950万曼联官宣签下利桑德罗马丁内斯,他成为红魔今夏第三签约。今年夏天,由于滕哈格的入主,曼联阵容进行了大换血。目前,球队引入3人,离队8人。引援方面,曼联先以1500欧从费耶诺德引入边后NBA10位改变联盟球星库里领衔,乔丹无解单打让联盟被迫改规则在NBA里判定一名球员的能力是否出众,我认为除了荣誉和纪录之外,能否让联盟修改规则改变打法也是同样关键,艾弗森只有一个常规赛MVP,没有总冠军,你能说他不如2个MVP的纳什吧?纳什萨默尔拜仁签30岁的马内但多特有年轻新援,为何不能想当第一直播吧7月18日讯多特球队顾问萨默尔在图片报的节目中表示,他们要认可拜仁今夏引援对实力的补强,而年轻球员的加盟也应该让多特敢于剑指第一。拜仁今夏已经签下马内,德里赫特也即将加盟。萨热火欲复刻詹韦时代,八换一交易杜兰特,新三巨头傲世联盟布鲁克林风云仍旧没有消停,NBA自由市场之十上依然有篮网和杜兰特的声音,鉴于之前篮网的要价太夸张,连NBA睿智的logo男都感叹很少有球队可以出得起能够获得KD的资源(主要是包括球比赛继续!国乒收获外卡,9位世界冠军征战WTT冠军赛,附今日赛程北京时间7月17日晚,布达佩斯WTT欧洲夏季系列赛之WTT球星挑战赛正式落下帷幕。国乒在本次比赛中收获颇丰,以4冠2亚成为最大赢家,其中王炸组合王楚钦和王曼昱则成为赛事仅有的两位双27岁的嘴哥,他真的还能有长足进步吗今年勇士夺冠后,就有很多人希望勇士能够留下维金斯,原因就是维金斯在今年季后赛的表现很出色,特别是总决赛期间,攻防一体,场均得分位居队内第二,防守端能量十足。维金斯在季后赛期间获得了盘点付出大量选秀权交易球星的成功案例湖人得AD火箭得哈登(译者注本文作者为SportingNews作家ScottRafferty,文中内容不代表译者和平台观点)凯文杜兰特。凯里欧文。多诺万米切尔。自由球员市场的热度可能正在下降,但涉及少吃鸡7月22日新军需实装效果!恐龙自带泳圈,尾巴长在腰间务实不浮夸!我是你们的情报小能手,微笑十倍镜。本期给大家分享一下,即将在7月22日上线的新军需实战效果。众所周知,吃鸡手游和平精英的新军需只要在近期上架,那么全套皮肤一定会在前几天
春夏半身裙怎么穿?选对款式,搭对鞋子,重新定义时尚感大家在春夏季节都喜欢穿什么样的裙子呢,我想这个问题的答案应该很多吧,每个人对于时尚的定义不同,对于单品的欣赏程度也是不同的,在春夏季节女人往往都会去挑选一些裙装。如果你在今年春夏要暗物质,如何证明其存在?暗物质看不见摸不着,如何探测其存在?暗物质是宇宙中的一种未知物质,它不会发光不会吸收光,也无法通过电磁辐射进行探测。然而,科学家们可以通过观察宇宙中的其他现象,推断出暗物质的存在。21道好吃的下饭菜呀,美味无敌下饭菜好吃又好做香芒鲜虾盅原料吕宋芒果10个,草虾仁200克,松子100克,杏仁100克,青豆100克。调料植物油精盐味精鸡精粉白糖水淀粉。做法1将草虾仁从脊背剞一刀芒果洗净,一剖两开,去核,将肉米色不仅自带高级感,还很百搭,用好这3点配色思路就够了头条创作挑战赛米色的经典不用多介绍了,大家对米色的认识和喜欢的程度,不会亚于黑色与白色。但是,当看到米色与其他颜色的配搭后,不得不被米色的百搭包容和塑造性所折服,并且带来的美,不仅AI对房地产有什么影响?8个建议帮你实现梦想房地产是一项充满机遇和挑战的行业,但现在,有一个新的机会来了,那就是人工智能(AI)。你可能会问,AI对房地产有什么影响吗?,我的答案是当然有!在本文中,分享8个建议,帮助那些想利剧情向催泪游戏推荐比起痛苦,美好的回忆更让人难以忘怀!既然点了进来,那我也不着急这么快安利游戏,我就先和大伙聊点废话,因为会特意去寻找催泪剧情游戏,或者说,看到标题的就忍不住点进来的,在内心或多或少都会有点但属于自己内心的那份压力吧,无论男女,洗澡时这4件事不能做,对健康无益洗澡是大多数人每天会做的事情,通过正确洗澡来去除身上的污垢,保持皮肤干净卫生,当然,一整天工作,学习人会感觉疲惫,能及时洗个热水澡会感觉身体轻松,后续去睡觉睡眠质量比较高,这些都是小白鞋已经过时了!今春乐福鞋才是鞋圈顶流,高级又百搭鞋子的搭配效果真的可以说是对于整体的身形状态和气质的衬托完美合一的存在啦,而对于不少的女性来说百搭又经典十足的小白鞋相信是心头爱的程度了吧,但是在今年的时尚趋向之中,小白鞋已经被淘伊藤美诚麻烦来了!这次连退路都打没了,必须胜一次中国选手北京时间4月5日,就在国际乒联公布最新一期的世界排名后,除了陈梦名次持续下跌备受关注外,被视为中国女乒最大对手的伊藤美诚,同样收到坏消息。因为东京奥运会等赛事的积分到期,让本次的排4支亚洲奥运队受邀参加土伦杯中国国奥队未收到参赛邀请北京时间4月5日下午,从法国土伦杯赛事组委会传来消息,第49届土伦杯国际青年足球邀请赛将于今年6月举办。包括4支亚洲球队在内,共有12支U23年龄段代表队参加本届赛事。由成耀东率领全国青锦赛开赛中国速滑在最快的冰检验后备力量京报体育记者赵晓松举办过北京冬奥会比赛的顶级场馆全国同年龄选手中的佼佼者精彩纷呈的开幕式表演4月5日,2022至2023赛季全国速度滑冰青年锦标赛在国家速滑馆冰丝带揭幕。167名来