【Java多线程】计时器Timer/ScheduledExecutorService的使用

🔍 开发者资源导航 🔍
🏷️ 博客主页个人主页
📚 专栏订阅JavaEE全栈专栏

Timer

Timer 是 Java 提供的一个简单的任务调度工具(位于 java.util 包),用于在指定的时间执行任务(一次性或周期性执行)。

Timer 的主要作用

  • 定时任务:在指定的延迟后执行一次任务。

  • 周期性任务:按照固定的时间间隔重复执行任务

Timer 的核心方法

方法 说明
schedule(TimerTask task, long delay) 延迟 delay 毫秒后执行一次任务
schedule(TimerTask task, Date time) 在指定的 Date 时间执行一次任务
schedule(TimerTask task, long delay, long period) 延迟 delay 毫秒后开始,每隔 period 毫秒重复执行任务
scheduleAtFixedRate(TimerTask task, long delay, long period) 延迟 delay 毫秒后开始,以固定速率(period 毫秒)重复执行任务
cancel() 终止 Timer,取消所有已安排的任务

使用实例

java 复制代码
import java.util.Timer;
import java.util.TimerTask;

public class demo18 {
    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 1000");
            }
        }, 1000); // 1秒后执行

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 2000");
            }
        }, 2000); // 2秒后执行

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        }, 3000); // 3秒后执行
    }
}
java 复制代码
 Timer timer = new Timer();

 timer.schedule(new TimerTask() {
     @Override
     public void run() {
         System.out.println("hello 1000");
     }
 }, 1000, 1000); // 每隔1s执行一次

缺点

  • 单线程执行:所有任务都在同一个线程中执行,如果一个任务执行时间过长,会影响其他任务的调度。

  • 异常影响全局:如果某个任务抛出未捕获的异常,整个 Timer 会终止,后续任务不会执行。

  • 时间精度依赖系统时钟:如果系统时间被调整(如手动修改时间),可能导致任务执行不准确。

ScheduledExecutorService

由于 Timer 的局限性,Java 5 引入了 ScheduledExecutorService(属于

java.util.concurrent 包),它使用线程池,更稳定、更灵活,推荐在新代码中使用。

创建方式

使用线程池的工厂类

java 复制代码
创建单线程的ScheduledExecutorService,任务量少,需顺序执行
Executors.newSingleThreadScheduledExecutor()

创建n个线程的ScheduledExecutorService,高并发任务,需并行执行
Executors.newScheduledThreadPool(n)

创建n个线程的ScheduledExecutorService,并设置拒绝方法
Executors。ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

直接创建

java 复制代码
可以设置工厂方法,拒绝方法,以及核心数目
ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler)

推荐使用

  • 如果只是普通定时任务 → 推荐 Executors.newScheduledThreadPool()(更简洁)。

  • 如果需要定制线程池(如调整拒绝策略、线程工厂) → 使用 new ScheduledThreadPoolExecutor()。

使用方法

使用 ScheduledExecutorService 进行任务调度时,可以通过不同的方法设置任务的执行方式,主要包括

延迟执行、固定速率执行 和 固定延迟执行。

延迟执行(单次任务)

参数

java 复制代码
schedule(Runnable command, long delay, TimeUnit unit)

特点

  • 任务 只会执行一次
  • 在指定的 delay 时间后执行。

使用场景:只需要在某个时间点执行一次的任务(如延迟初始化、超时任务)。

示例:

java 复制代码
executor.schedule(() -> {
    System.out.println("Task runs after 3 seconds");
}, 3, TimeUnit.SECONDS);

固定速率执行(Fixed Rate)

参数:

java 复制代码
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

特点:

  • 按照固定频率执行,不受任务执行时间影响。

  • 如果任务执行时间超过 period,下一次任务会 立即开始(可能并发执行)。

使用场景:适用于 严格周期性的任务(如定时数据同步)。

示例:

java 复制代码
// 初始延迟 1 秒,之后每 2 秒执行一次(不管任务执行多久)
executor.scheduleAtFixedRate(() -> {
    System.out.println("Fixed Rate Task - " + System.currentTimeMillis());
}, 1, 2, TimeUnit.SECONDS);

固定延迟执行(Fixed Delay)

参数:

java 复制代码
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

特点:

  • 任务执行完成后,再等待 delay 时间后执行下一次。

使用场景:适用于 需要保证任务执行间隔 的场景(如心跳检测)

示例:

java 复制代码
// 初始延迟 1 秒,每次任务结束后等 2 秒再执行下一次
executor.scheduleWithFixedDelay(() -> {
    System.out.println("Fixed Delay Task - " + System.currentTimeMillis());
}, 1, 2, TimeUnit.SECONDS);

手写一个Timer

参数

java 复制代码
 Object locker = new Object();//锁
 private PriorityQueue<MytimerTask> queue = new PriorityQueue<>();

通过优先队列,来对时间进行排列。

schedule

java 复制代码
 public void schedule(Runnable task, long delay) {
     synchronized (locker) {
         queue.offer(new MytimerTask(task, System.currentTimeMillis() + delay));
         locker.notify();
     }
 }

初始化方法

java 复制代码
 public Mytimer() {
     Thread t1 = new Thread(()->{
         synchronized (locker){
             try {
                 while (true) {
                     while (queue.size() == 0) {
                     //避免没有任务时的忙等
                             locker.wait();
                     }
                     if (queue.peek().time <= System.currentTimeMillis()) {
                         queue.peek().runnable.run();
                         queue.poll();
                     } else {
                     //此处也是为了避免忙等
                         //等待时间,虽然可能会错误唤醒,但是影响几乎可以忽略不计。
                         locker.wait(queue.peek().time - System.currentTimeMillis());
                     }
                 }
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     });
     t1.start();
 }

此处的wait不能使用sleep来代替

  1. sleep的时候不会释放锁,到时候添加不了新的任务。
  2. sleep时长是固定的,如果等待时候添加了一个时间更早的任务,可能导致无法正确执行。而wait在添加新任务的时候会被唤醒一次。

感谢各位的观看Thanks♪(・ω・)ノ,如果觉得满意的话留个关注再走吧。

相关推荐
依旧阳光的老码农9 分钟前
Qt SQL 核心类说明文档
开发语言·sql·qt
小梦白41 分钟前
RPG7.准备GAS的工作
java·开发语言
武昌库里写JAVA43 分钟前
【iview】icon样式
java·开发语言·spring boot·学习·课程设计
不太可爱的叶某人1 小时前
【学习笔记】深入理解Java虚拟机学习笔记——第1章 走进Java
java·jvm·笔记·学习
颇有几分姿色1 小时前
Spring Boot 实现多种来源的 Zip 多层目录打包下载(本地文件&HTTP混合)
java·spring boot·后端
百锦再1 小时前
Android Studio中OpenCV应用详解:图像处理、颜色对比与OCR识别
android·java·图像处理·opencv·kotlin·app·android studio
-XWB-1 小时前
【Java】打印运行环境中某个类引用的jar版本路径
java·开发语言
Cuit小唐1 小时前
C++ 单例模式详解
开发语言·c++·单例模式
Brookty2 小时前
【Java学习】通配符?
java·学习
正在走向自律2 小时前
Python面向对象编程实战:从类定义到高级特性的进阶之旅(2/10)
开发语言·python·面向对象·python基础知识