好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ScheduledThreadPoolExecutor巨坑解决

概述

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

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

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

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

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

坑是啥?

先看下面案例代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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

?

1

2

3

4

5

6

7

8

9

10

11

public class RunnableDecoratorUtil {

    public static Runnable runnableDecorator(Runnable runnable) {

       return () -> {

          try {

             runnable.run();

          } catch (Exception e) {

             e.printStackTrace();

          }

       };

    }

}

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

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

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

怎么坑的?

直接进入 scheduleAtFixedRate 源码查看

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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 方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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 源码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

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

原文链接:https://juejin.cn/post/7202066794298228794

查看更多关于ScheduledThreadPoolExecutor巨坑解决的详细内容...

  阅读:14次