java-Timer和TimerTask异常

387 阅读2分钟

问题

如果定时任务异常,会发生什么?后面还会继续执行吗?

结论

不会。

源码分析

为什么不会继续执行?

核心代码如下。核心作用是,调用线程循环执行业务线程。

java.util.TimerThread#mainLoop

/**
     * 调度线程:循环执行业务线程
     *
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait(); //如果队列为空,就等待
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die


                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    //获取时间最早的业务线程
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) { //如果业务线程状态被取消,那么删除业务线程
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) { //判断任务还未触发(因为时间没到)
                            if (task.period == 0) { //非重复执行任务,会删除任务 // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { //重复执行任务,不会删除任务 // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime); //如果任务还未触发(因为时间没到),也等待
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    //如果时间到了,就执行任务线程
                    task.run(); //
            } catch(InterruptedException e) {
            }
        }
    }

核心代码,看这行:

//如果时间到了,就执行任务线程
                    task.run(); //

问题就在这里,这里会执行业务线程。

如果业务线程异常了,会导致java.util.TimerThread#mainLoop方法里的while循环退出了,因为while循环里的代码异常了。

while循环退出之后,本质其实是调度线程结束了,定时器Timer也就不能继续处理业务线程了。

而,正常情况下,调度线程其实是应该一直循环执行业务线程。

demo

通过模拟异常,来模拟测试。

具体来说是:

  1. 模拟业务线程异常

异常的时候,会抛出异常。

  1. 业务线程抛出异常,会导致调度线程退出while循环

其实本质是调度线程结束了,就不能继续处理业务线程了。


测试代码

@Test
    public void givenUsingTimer_whenSchedulingRepeatedTask_thenCorrect2()
        throws InterruptedException, IOException { //重复执行:模拟异常
        //创建调度器
        final TimerTask repeatedTask = new TimerTask() {
            @Override
            public void run() {
                LOG.info("Task performed on " + new Date());
                int i = 1 / 0; //模拟异常
            }
        };


        //执行任务线程
        final Timer timer = new Timer("Timer");
        final long delay = 3000L;
        final long period = 1000L;
        try {
            timer.scheduleAtFixedRate(repeatedTask, delay, period);
        } catch (Exception e) {
            LOG.error("异常",e);
        }


        //
        System.in.read();
//        Thread.sleep(delay * 2);
//        timer.cancel();
    }

测试结果

11:15:57.417 [Timer] INFO  c.b.t.JavaTimerLongRunningUnitTest - Task performed on Fri Aug 19 11:15:57 CST 2022
Exception in thread "Timer" java.lang.ArithmeticException: / by zero
	at com.baeldung.timer.JavaTimerLongRunningUnitTest$3.run(JavaTimerLongRunningUnitTest.java:79)
	at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
	at java.base/java.util.TimerThread.run(Timer.java:506)

总结

业务线程异常之后,会导致调度线程退出while循环 image.png

退出while循环之后,会执行到这里: java.util.TimerThread#run

public void run() {
    try {
        mainLoop(); //业务线程异常之后,会导致调度线程退出while循环
    } finally { //然后,继续往下执行
        // Someone killed this Thread, behave as if Timer cancelled
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
        }
    }
}

打了断点如下,已经执行到finally代码,说明while循环已经退出了 image.png