在做码农的日子里面,如果不跟线程打交道,那真的连入门都算不上了,如果你还仅仅是简单的new Thread,那么你就是跟我一样的小白了;怎么也得弄点高大上的线程池吧,
用线程池肯定就少不了java concurrent包中的ExecutorService了;这里面的学问还是挺大的。以后有机会慢慢品读;在你的任务中,肯定也有定时任务的吧,如果你的定时还用Timer的化,
那么你真的就跟我一样out了,具体原因请google下;说到Java的定时任务,肯定是非ScheduledExecutorService莫属了。这个用法是相当简单的。。。

正常运行



package qhl.silver.ScheduledExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class BadAssTask implements Runnable {

    public void run() {
        System.out.println("开始做任务了,好开心。。。。");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("没有有bug(error/exception)出现,我可以很开心的继续执行了 ");
    }

    public static void main(String[] args) {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new BadAssTask(), 1, 1, TimeUnit.SECONDS);
    }

}

上述任务会每秒钟定时执行,输出如下所示:

java 定时任务取消定时任务执行 java定时任务失效_java 定时任务取消定时任务执行

遇到异常情况

但是,假如你的任务有问题呢?有bug,有异常呢?请看如下测试代码



package qhl.silver.ScheduledExecutorService;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class BadAssTask implements Runnable {

    public void run() {
        System.out.println("开始做任务了,好开心。。。。");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("如果有bug(error/exception)出现话,这个定时任务还能不能继续执行呢? ");
        throw new RuntimeException("卧槽。。出现bug了,你竟然不catch!!!!!");
    }

    public static void main(String[] args) {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new BadAssTask(), 1, 1, TimeUnit.SECONDS);
    }

}

以上测试代码就会出现如下输出,也就是仅仅运行了一次就挂了。这不符合预期啊!

java 定时任务取消定时任务执行 java定时任务失效_java_02

问题原因查找

打开jdk的官方文档!, 可以看到如下解释

java 定时任务取消定时任务执行 java定时任务失效_java 定时任务取消定时任务执行_03


以上标注内容翻译为人话就是:如果执行过程中遇到了问题(error/exception),那么后面的定时任务也就不会继续执行了

这显然不符合预期啊。这简直就是fuck egg的事情,哪有程序不会遇到点bug,遇到点异常呢?当时执行遇到异常,也许以后就好了呢?毕竟编程是一门神学,你不能因为一次异常,而放弃执行之后的定时任务啊!!!!

解决办法

那怎么解决这个问题呢。很显然,既然ScheduledExecutorService有可能在运行任务的过程中。任务(继承Runnable接口的)有可能抛出异常,那就catch这个异常呗。

方法1

在run方法的外部,使用try catch语句catch可能的异常,仅仅catch 异常(Exception)还是不够的,有可能还有error,所以都需要catch的,代码如下


try {
     throw new RuntimeException("卧槽。。出现bug了,你竟然不catch!!!!!");
 } catch (Error e) {
     System.out.println("error occurred, e=" + e);
 } catch (Exception e) {
     System.out.println("exception occurred, e=" + e);
 }

当然了,由于Error和Exception都继承了Throwable,所以,只需要catch Throwable一个就可以了,所以以上代码可以简化为如下形式:


try {
    throw new RuntimeException("卧槽。。出现bug了,你竟然不catch!!!!!");
} catch (Throwable t) {
    System.out.println("some thing wrong happened, error=" + t);
}

但是在每个run方法中都要try catch,也是很痛苦的事情,这得多了多少代码啊!!!此时,方法2就要现身了!!!

方法2

编写一个wrap类,封装这个ScheduledThreadPoolExecutor,在这个类里面进行try/catch。这样外部就不用try/catch了;当然你也可以在这个类里面把异常继续向上抛出,如果选择继续把异常向上抛出,那么外部必须选择try/catch此异常,否则,还是会造成后续定时任务不会执行


package qhl.silver.ScheduledExecutorService;

import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class WrappingScheduledExecutor extends ScheduledThreadPoolExecutor {

    public WrappingScheduledExecutor(int corePoolSize) {
        super(corePoolSize);
    }

    @Override
    public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        return super.scheduleAtFixedRate(wrapRunnable(command), initialDelay, period, unit);
    }

    @Override
    public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        return super.scheduleWithFixedDelay(wrapRunnable(command), initialDelay, delay, unit);
    }

    private Runnable wrapRunnable(Runnable command) {
        return new LogOnExceptionRunnable(command);
    }

    private class LogOnExceptionRunnable implements Runnable {
        private Runnable theRunnable;

        public LogOnExceptionRunnable(Runnable theRunnable) {
            super();
            this.theRunnable = theRunnable;
        }

        public void run() {
            try {
                theRunnable.run();
            } catch (Throwable t) {
                System.err.println("error in executing: " + theRunnable + ", the error = " + t);
                /**
                 *重要,如果你选择继续向上抛出异常,则在外部必须能够catch住这个异常,否则还是会造成后续任务不会执行
                 * IMPORTANT: if you thrown exception. then in the out, you have to try/catch the exception,
                 * otherwise, the executor will stop
                 */
                // throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        new WrappingScheduledExecutor(1).scheduleAtFixedRate(new BadAssTask(), 1, 1, TimeUnit.SECONDS);
    }
}

问题延伸-看看知名开源软件怎么玩的

google guava 中的WrappingExecutorService,WrappingScheduledExecutorService也是简单的封装了,而且是abstract类,用户还无法直接使用,必须要有一个实现类implement这个类方可使用。思想与上述解决方案2一致。

画外音

线程池中的ExecutorService也是有这个问题的。请看如下代码,就如同注释中说的,如果不try catch,则如果遇到问题就不会继续执行了。


package qhl.silver.ScheduledExecutorService;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestExecutor {
    private ExecutorService executeProcessor;

    TestExecutor() {
        executeProcessor = Executors.newSingleThreadScheduledExecutor();
        executeProcessor.execute(this::taskRun);
    }

    public void taskRun() {
        while (true) {
            System.out.println("正常执行");
            try {
                Thread.sleep(1 * 1000);
                throw new RuntimeException("出错了,但是被我catch住之后,还是会继续执行的!!!");
            } catch (Throwable e) {
                System.out.println("error occurred = " + e);
            }
            System.out.println("由于下面的错误没有被catch,所以这个任务就不会被继续执行了");
            //            throw new RuntimeException("出错了,没有被catch,所以就不会继续执行了!!!");
        }
    }

    public static void main(String args[]) {
        new TestExecutor();
    }
}

结论

凡事使用ExecutorService的,都要try catch

参考文献

ScheduledExecutorService Exception handlingMother F**k the ScheduledExecutorService!