java.util.Timer定时任务原理(建议使用java.util.concurrent.Executors)

本文详细解析了`java.util.Timer`类的工作原理,包括内部的任务队列、`TimerThread`线程的启动、`TimerTask`的执行过程,以及如何通过`cancel()`方法停止定时任务。在理解其工作方式的基础上,建议使用`java.util.concurrent.Executors`来替代`Timer`,以获得更好的并发控制和资源管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {
    public static void main(String[] args) throws InterruptedException {

        //创建一个定时器
        Timer timer = new Timer();
        //创建任务内容
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId());
            }
        };
        //设置多久开始运行,隔多久运行
        timer.schedule(timerTask, 1000, 1000);
        //主线程休眠一段时间
        Thread.sleep(11100);
        //手动停止定时器
        timer.cancel();
    }
}

原理:

1、先来看下Timer类的大概描述:

import java.util.TaskQueue;
import java.util.TimerThread;

public class Timer {

    private final TaskQueue queue = new TaskQueue();

    private final TimerThread thread = new TimerThread(queue);

    private final Object threadReaper = new Object() {
        protected void finalize() throws Throwable {
            synchronized (queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify(); // In case queue is empty.
            }
        }
    };

    //other things...
}

可以看到,Timer定时器内部包含了个TaskQueue队列,专门用来存储添加进来的任务。

同时内部还包含了个class TimerThread extends Thread,TimerThread是一个新的线程,所有队列里的任务,都是通过这个线程调用执行。

2、再来看下new Timer()做了啥,下面是构造方法:

    public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
    }

重点在于thread.start(),也就是说只要创建了Timer对象,Timer内部的TimerThread线程就已经启动。

3、再来看public abstract class TimerTask implements Runnable,可知TimerTask是一个可以被线程调用的Runnable。

当执行timer.schedule(timerTask, 1000, 1000);,代码逻辑如下:

    void schedule(TimerTask task, long time, long period) {
        //do SomeThings

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                            "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }

            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

其实就两个步骤:
第一个是queue.add(task);把任务加到队列
第二个是queue.getMin() == task判断队列头是不是当前任务,如果是直接让队列所在的线程跑起来:queue.notify();就是去唤醒TimerThread线程

4、TimerThread线程为什么需要唤醒呢?且看下面的TimerThread线程的run方法:

class TimerThread extends Thread {
    public void run() {
        try {
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }
}

深入看mainLoop()方法:


    void mainLoop() {
        //简化后的代码
        while (true) {
            TimerTask task;
            boolean taskFired;
            synchronized (queue) {
                // Wait for queue to become non-empty
                while (queue.isEmpty() && newTasksMayBeScheduled){
                    queue.wait(); //队列空了,让线程等待
                }
                if (queue.isEmpty()){
                    break; // 跳出循环,
                }
                // Queue nonempty; look at first evt and do the right thing
                long currentTime, executionTime;
                task = queue.getMin();
                synchronized (task.lock) {
                    //判断当前task是否轮到执行时间
                    taskFired = true;
                }
            }
            if (taskFired)  // Task fired; run it, holding no locks
                task.run();
        }
    }

上面说到new Timer()立马就启动了线程thread.start(),启动线程就会执行run方法,run方法就会调用mainLoop方法,看到里面判断如果队列为空while (queue.isEmpty())则让线程等待queue.wait()

所以每次添加任务进来,如果轮到当前任务执行,那么首先就是唤醒线程,然后线程继续执行队列中的任务。

因为这while (true)是一个无限循环的去执行队列任务的方法,只要任务队列不为空,则一定会按顺序被单线程TimerThread调用任务的run方法。

其实,Timer的创建,任务添加到队列,都是在主线程完成的。但是队列中任务的执行,却是在TimerThread.run()方法中执行,所以就是TimerThread线程跑了。

5、如果定时任务队列为空,Timer也不会自动停止,需要手动停止:
如上面代码所示:timer.cancel();,它的实现如下:

    public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

对比mainLoop方法可以知道:设置thread.newTasksMayBeScheduled = false;之后,已经不满足while (queue.isEmpty() && newTasksMayBeScheduled),但是满足if (queue.isEmpty()),所以直接break跳出循环了,因此能够结束timer。

那如果不自己执行cancel方法呢?看上面timer类的一个属性threadReaper

    private final Object threadReaper = new Object() {
        protected void finalize() throws Throwable {
            synchronized (queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify(); // In case queue is empty.
            }
        }
    };

创建一个对象并重写finalize()方法,目的就是,当主线程中没有对Timer的引用时,当垃圾回收发生时,就会回收这个Timer对象,从而回收threadReaper对象,从而执行其finalize方法,注意到finalize方法和cancel方法类似,都thread.newTasksMayBeScheduled = false;,如果队列为空,那么就会直接跳出循环,从而结束timer定时器线程的生命周期。

org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class [com.freshlife.willtech.chatkernel.timer.NFDFlightDataTaskListener] java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/JsonProcessingException at com.freshlife.willtech.chatkernel.timer.NFDFlightDataTaskListener.contextInitialized(NFDFlightDataTaskListener.java:45) at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4792) at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5256) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:754) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:730) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734) at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:985) at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1857) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.ClassNotFoundException: com.fasterxml.jackson.core.JsonProcessingException at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1308) at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1136) ... 14 more
06-10
Nifi插入数据到mysql中报错2025-03-04 13:54:58,620 ERROR [Timer-Driven Process Thread-9] o.a.n.p.standard.PutDatabaseRecord PutDatabaseRecord[id=5f22e3ee-0195-1000-de57-028d5c69d022] Failed to put Records to database for StandardFlowFileRecord[uuid=6e5d7433-3a31-4f27-a695-f0e20ae72137,claim=StandardContentClaim [resourceClaim=StandardResourceClaim[id=1741050733753-1, container=default, section=1], offset=650, length=65],offset=0,name=6e5d7433-3a31-4f27-a695-f0e20ae72137,size=65]. Routing to failure. java.sql.SQLDataException: None of the fields in the record map to the columns defined by the trq_ssql table Normalized Columns: ID,TDATE,SALESGAS,CREATETIME,UPDATETIME at org.apache.nifi.processors.standard.PutDatabaseRecord.generateInsert(PutDatabaseRecord.java:1102) at org.apache.nifi.processors.standard.PutDatabaseRecord.executeDML(PutDatabaseRecord.java:666) at org.apache.nifi.processors.standard.PutDatabaseRecord.putToDatabase(PutDatabaseRecord.java:998) at org.apache.nifi.processors.standard.PutDatabaseRecord.onTrigger(PutDatabaseRecord.java:492) at org.apache.nifi.processor.AbstractProcessor.onTrigger(AbstractProcessor.java:27) at org.apache.nifi.controller.StandardProcessorNode.onTrigger(StandardProcessorNode.java:1361) at org.apache.nifi.controller.tasks.ConnectableTask.invoke(ConnectableTask.java:247) at org.apache.nifi.controller.scheduling.TimerDrivenSchedulingAgent$1.run(TimerDrivenSchedulingAgent.java:102) at org.apache.nifi.engine.FlowEngine$2.run(FlowEngine.java:110) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.
03-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值