开发者

Java Timer单线程下的定时任务举例详解

开发者 https://www.devze.com 2025-10-30 10:25 出处:网络 作者: 吗喽什么时候秃头
目录引言什么是小顶堆Timer 类的核心结构任务队列(TaskQueue):基于小顶堆的实现调度线程(TimerThread):任务执行的核心逻辑代码使用演示运行结果Timer的缺点适用场景与替代方案总结引言
目录
  • 引言
  • 什么是小顶堆
  • Timer 类的核心结构
  • 任务队列(TaskQueue):基于小顶堆的实现
  • 调度线程(TimerThread):任务执行的核心逻辑
  • 代码使用演示
    • 运行结果
  • Timer的缺点
    • 适用场景与替代方案
      • 总结

        引言

        Timer 是 Java 标准库(java.util 包)中用于 定时调度任务 的工具类,它允许程序在指定时间点执行任务,或按固定周期重复执行任务。简单来说,Timer 就像一个 “定时器”,可以帮你规划代码在未来的某个时间运行,或周期性地运行。

        在学习 Timer 的用法和底层原理前,最需要了解的核心数据结构是 优先级队列,具体来说是 Timer 内部使用的 基于小顶堆实现的优先级队列

        什么是小顶堆

        小顶堆是一颗完全二叉树,它的根节点是最小的元素,然后每个子节点都必须比它的父节点存的值要大。

        Java Timer单线程下的定时任务举例详解

        在 Timer 中,小顶堆(TaskQueue)的堆顶元素是 “最早需要执行的定时任务(TimerTask)”。

        具体来说,TaskQueue 会按照任务的 nextExecutionTime(下一次执行时间)对所有 TimerTask 进行排序,而小顶堆的堆顶元素就是 nextExecutionTime 最小的任务,也就是当前所有任务中 “最早到点需要执行” 的那个任务。

        Timer 类的核心结构

        public class Timer {
            // 任务队列(基于小顶堆实现)
            private final TaskQueue queue = new TaskQueue();
            // 调度线程(负责执行任务)
            private final TimerThread thread = new TimerThread(queue);
            
            // 构造方法:启动调度线程
            public Timer() {
                this("Timer-" + serialNumber());
            }
            
            public Timer(String name) {
                thread.setName(name);
                thread.start(); // 启动TimerThread
            }
            
            // 提交任务的核心方法(以schedule为例)
            public void schedule(TimerTask task, Date time) {
                // 计算延迟时间(相对于当前时间)
                long delay = time.getTime() - System.currentTimeMillis();
                if (delay < 0) delay = 0;
                // 调用内部方法添加任务
                sched(task, System.currentTimeMillis() + del编程ay, 0);
            }
            
            // 实际添加任务到队列的方法
            private void sched(TimerTask task, long time, long period) {
                if (time < 0) throw new IllegalArgumentException("Illegal execution time.");
                
                // 同步锁:保证任务队列操作的线程安全
                synchronized(queue) {
                    if (thread.isInterrupted())
                        throw new IllegalStateException("Timer already cancelled.");
                    
                    // 设置任务的执行时间和周期
                    task.nextExecutionTime = time;
                    task.period = period;
                    task.state = TimerTask.SCHEDULED;
                    
                    // 将任务添加到队列(小顶堆)
                    queue.add(task);
                    // 如果添加的是堆顶任务,唤醒调度线程(可能在等待中)
                    if (queue.getMin() == task)
                        queue.notify();
                }
            }
        }

        任务队列(TaskQueue):基于小顶堆的实现

        在Java中,不管是二叉树,还是堆,它们都只是逻辑结构,并不是真正意义上的存储结构,具体的存储结构都是基于数组或者链表来实现的。

        class TaskQueue {
            // 用数组存储堆元素(小顶堆)
            private TimerTask[] queue = new TimerTask[128];
            private int size = 0; // 当前任务数量
            
            // 添加任务(入队)
            void add(TimerTask task) {
                // 扩容逻辑(当数组满时)
                if (size + 1 == queue.length)
                    queue = Arrays.copyOf(queue, 2 * queue.length);
                
                queue[size++] = task;
                // 上浮调整:维持小顶堆特性
                fixUp(size - 1);
            }
            
            // 获取堆顶任务(最早执行的任务)
            TimerTask getMin() {
                return queue[0];
            }
            
            // 移除堆顶任务(出队)
            void removeMin() {
                queue[0] = queue[--size];
                // 下沉调整:维持小顶堆特性
                fixDown(0);
            }
            
            // 上浮操作:新元素插入后,向上调整堆
            private void fixUp(int k) {
                while (k > 0) {
                    int j = (k - 1) >>> 1; // 父节点下标
                    if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                        break; // 父节点更小,无需调整
                    // 交换父节点和当前节点
                    TimerTask tmp = queue[j];
                    queue[j] = queue[k];
                    queue[k] = tmp;
                    k = j;
                }
            }
            
            //yyJrERkh 下沉操作:移除堆顶后,向下调整堆
            private void fixDown(int k) {
                int j;
                while ((j = (k << 1) + 1) < size) { // 左子节点下标
                    // 找到左右子节点中较小的那个
                    if (j + 1 < size && queue[j + 1].nextExecutionTime < queue[j].nextExecutionTime)
                        j++;
                    if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                        break; // 当前节点更小,无需调整
                    // 交换当前节点和子节点
                    TimerTask tmp = queue[k];
                    queue[k] = queue[j];
                    queue[j] = tmp;
                    k = j;
                }
            }
        }

        调度线程(TimerThread):任务执行的核心逻辑

        TimerThread 是 Timer 的内部线程类,负责从队列中取任务并执行:

        class TimerThread extends Thread {
            private final TaskQueue queue;
            
            TimerThread(TaskQueue queue) {
                this.queue = queue;
            }
            
            public void run() {
                try {
                    mainLoop(); // 核心循环:不断执行任务
                } finally {
                    // 线程退出时,标记所有任务为取消状态
                    synchronized(queue) {
                        queue.isDisposed = true;
                        queue.queue = null;
                    }
                }
            }
            
            // 核心循环:调度任务的主逻辑
            private void mainLoop() {
                while (true) {
                    try {
                        TimerTask task;
                        boolean taskFired;
                        
                        // 同步获取任务
                        synchronized(queue) {
                            // 等待队列中有任务
                            while (queue.size() == 0 && !queue.isDisposed)
                                queue.wait();
                            if (queue.isDisposed)
                                break; // 队列已销毁,退出循环
                            
                            // 获取堆顶任务(最早执行的任务)
                            long currentTime, executionTime;
                            task = queue.getMin();
                            synchronized(task.lock) {
                                if (task.state != TimerTask.SCHEDULED) {
                                    queue.removeMin(); // 任务已取消,移除
                                    continue;
                                }
                                currentTime = System.currentTimeMillis();
                                executionTime = task.nextExecutionTime;
                                // 检查是否到达执行时间
                                if (executionTime > currentTime) {
                                    // 未到时间:等待差值时间
                                    queue.wait(executionTime - currentTime);
                                    taskFired = false;
                                } else {
                                    // 已到时间:移除堆顶任务
                                    queue.removeMin();
                                    taskFired = true;
                                }
                            }
                        }
                        
                        //编程客栈 执行任务(如果已到时间)
                        if (taskFired) {
                            task.run(); // 调用TimerTask的run()方法
                            // 处理周期性任务:重新计算下次执行时间并加入队列
                            if (task.period != 0) {
                                synchronized(task.lock) {
                                    task.nextExecutionTime = 
                                        task.period < 0 ? currentTime - task.period 
                                                        : executionTime + task.period;
                                    queue.add(task); // 重新入队
                                }
                            }
                        }
                    } catch (InterruptedException e) {
                        // 忽略中断,继续循环
                    }
                }
            }
        }

        代码使用演示

        package com.ape.test;
        
        import java.util.Date;
        import java.util.Timer;
        import java.util.TimerTask;
        
        public class Test {
            public static void main(String[] args) {
                Timer timer = new Timer();
                final int totalTasks = 3; // 总任务数
        
                for(int i = 0; i < totalTasks; i++){
                    TimerTask task = new FooTimerTask("task" + i, timer, totalTasks);
                    timer.schedule(task, new Date());
                }
            }
        }
        
        class FooTimerTask extends TimerTask{
            private String name;
            private Timer timer;
            private int totalTasks;
            private static int completedCount = 0; // 已完成任务计数(静态变量共享)
        
            public FooTimerTask(String name, Timer timer, int totalTasks) {
                this.name = name;
                this.timer = timer;
                this.totalTasks = totalTasks;
            }
        
            @Override
            public void run() {
                try {
                    System.out.println("name:" + name + " startTime=" + new Date());
                    Thread.sleep(3000);
                    System.out.println("end:" + name + " endTime=" + new Date());
                    System.out.println("----------------------------------------");
        
                    // 任务完成后计数,最后一个任务完成时取消Timer,如果不取消Timer,进程不会结束
                    synchronized (FooTimerTask.class) {
                        completedCount++;
                        if (completedCount == totalTasks) {
                            timer.cancel();
                            System.out.println("所有任务执行完毕,Timer已终止");
                        }
                编程客栈    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        

        运行结果

        Java Timer单线程下的定时任务举例详解

        Timer的缺点

        1. 单线程串行执行,效率低所有任务依赖唯一的 TimerThread 执行,若某个任务耗时过长(如 IO 阻塞、长时间计算),会阻塞后续所有任务,即使它们已到执行时间。例:任务 A 执行需 10 秒,任务 B 本应在 2 秒后执行,实际会延迟到 A 结束后才开始。

        2. 异常敏感,易导致整体崩溃若某个 TimerTask 的 run() 方法抛出未捕获异常,会直接导致 TimerThread 终止,所有未执行的任务全部失效,且不会有任何提示。

        3. 时间依赖系统时钟,稳定性差任务的执行时间依赖系统时间,若系统时间被手动调整(如向前回拨),可能导致任务执行混乱(如周期性任务被重复执行)。

        4. 不支持多线程并发无法利用多核 CPU 资源,所有任务只能串行执行,不适合高并发或任务量大的场景。

        5. 线程生命周期管理繁琐Timer 线程默认是用户线程,若不主动调用 timer.cancel(),即使所有任务执行完毕,线程也会一直运行,导致程序无法退出。

        适python用场景与替代方案

        • 适合场景:单线程、任务执行时间短、无并发需求的简单定时任务(如简单的延迟提醒、临时调度)。
        • 不适合场景:多任务并发、任务耗时较长、需要高稳定性的场景(如分布式定时任务、核心业务调度)。
        • 推荐替代方案: ScheduledExecutorService,基于线程池实现,解决了 Timer 的单线程瓶颈和异常敏感问题,支持多线程并行执行,是更现代、更健壮的选择。

        在下一篇文章中说说ScheduleExecutorService

        总结

        到此这篇关于Java Timer单线程下的定时任务的文章就介绍到这了,更多相关Java Timer单线程定时任务内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        关注公众号