今天小编给大家分享一下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(); } }; } }
直接进入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
,所以我们直接来看ScheduledFutureTask
的run
方法
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); }
以上就是“ScheduledThreadPoolExecutor的坑如何解决”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。