Java定时器(Timer)

Java定时器与ExecutorService实战:任务调度与管理
本文详细介绍了Java中的Timer和TimerTask用于调度一次性任务和可重复任务的方法,包括固定延迟和固定频率。同时,对比了Timer与ExecutorService的使用,并展示了如何取消任务和定时器。涵盖了关键概念如定时器的延迟执行、定期执行、任务取消等。

1.介绍

Timer和TimerTask是用于在后台线程中调度任务的java util类。简单地说,TimerTask是要执行的任务,Timer是调度器。

2.调度一次性任务

2.1 指定延迟后执行

让我们从简单地在定时器的帮助下运行单个任务开始:

@Test
public void givenUsingTimer_whenSchedulingTaskOnce_thenCorrect() {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on: " + new Date() + "n" +
              "Thread's name: " + Thread.currentThread().getName());
        }
    };
    Timer timer = new Timer("Timer");
    
    long delay = 1000L;
    timer.schedule(task, delay);
}

延迟时间作为schedule()方法的第二个参数给出。我们将在下一节中了解如何在给定的日期和时间执行任务。

注意,如果我们正在运行这是一个JUnit测试,我们应该添加一个Thread.sleep(delay*2)调用,以允许定时器的线程在JUnit测试停止执行之前运行任务。

2.2 指定时间执行

现在,让我们看看Timer#schedule(TimerTask,Date)方法,它将日期而不是long作为其第二个参数,这实现了在某个时刻而不是在延迟之后执行任务。

这一次,让我们假设我们有一个旧的遗留数据库,我们希望将它的数据迁移到一个具有更好模式的新数据库中。我们可以创建一个DatabaseMigrationTask类来处理该迁移:

public class DatabaseMigrationTask extends TimerTask {
    private List<String> oldDatabase;
    private List<String> newDatabase;

    public DatabaseMigrationTask(List<String> oldDatabase, List<String> newDatabase) {
        this.oldDatabase = oldDatabase;
        this.newDatabase = newDatabase;
    }

    @Override
    public void run() {
        newDatabase.addAll(oldDatabase);
    }
}

为简单起见,我们用字符串列表来表示这两个数据库。简单地说,我们的迁移就是将第一个列表中的数据放到第二个列表中。要在所需的时刻执行此迁移,我们必须使用schedule()方法的重载版本:

List<String> oldDatabase = Arrays.asList("Harrison Ford", "Carrie Fisher", "Mark Hamill");
List<String> newDatabase = new ArrayList<>();

LocalDateTime twoSecondsLater = LocalDateTime.now().plusSeconds(2);
Date twoSecondsLaterAsDate = Date.from(twoSecondsLater.atZone(ZoneId.systemDefault()).toInstant());

new Timer().schedule(new DatabaseMigrationTask(oldDatabase, newDatabase), twoSecondsLaterAsDate);

我们将迁移任务和执行日期赋予schedule()方法。然后,在twoSecondsLater指示的时间执行迁移:

while (LocalDateTime.now().isBefore(twoSecondsLater)) {
    assertThat(newDatabase).isEmpty();
    Thread.sleep(500);
}
assertThat(newDatabase).containsExactlyElementsOf(oldDatabase);

虽然我们在这一刻之前,迁移并没有发生。

3.调度一个可重复执行任务

既然我们已经讨论了如何安排任务的单个执行,那么让我们看看如何处理可重复的任务。同样,Timer类提供了多种可能性:我们可以将重复设置为观察固定延迟或固定频率。

  • 固定延迟:意味着执行将在最后一次执行开始后的一段时间内开始,即使它被延迟(因此它本身被延迟)。假设我们想每两秒钟安排一个任务,第一次执行需要一秒钟,第二次执行需要两秒钟,但是延迟了一秒钟。然后,第三次执行将从第五秒开始:

  • 固定频率:意味着每次执行都将遵守初始计划,无论之前的执行是否被延迟。让我们重用前面的示例,使用固定的频率,第二个任务将在3秒钟后开始(因为延迟)。但是,四秒钟后的第三次执行(关于每两秒钟执行一次的初始计划):

关于这两种调度方式,让我们看看如何使用它们:

为了使用固定延迟调度,schedule()方法还有两个重载,每个重载都使用一个额外的参数来表示以毫秒为单位的周期性。为什么两次重载?因为仍然有可能在某个时刻或某个延迟之后开始执行任务。

至于固定频率调度,我们有两个scheduleAtFixedRate()方法,它们的周期也是以毫秒为单位的。同样,我们有一种方法可以在给定的日期和时间启动任务,还有一种方法可以在给定的延迟后启动任务。

注意一点:如果一个任务的执行时间超过了执行周期,那么无论我们使用固定延迟还是固定速率,它都会延迟整个执行链。

3.1 固定延迟

现在,让我们设想一下,我们要实现一个通讯系统,每周向我们的追随者发送一封电子邮件。在这种情况下,重复性任务似乎是理想的。所以,让我们安排每秒钟的通讯,这基本上是垃圾邮件,但由于发送是假的,所以不用在意:)

让我们首先设计一个任务:

public class NewsletterTask extends TimerTask {
    @Override
    public void run() {
        System.out.println("Email sent at: " 
          + LocalDateTime.ofInstant(Instant.ofEpochMilli(scheduledExecutionTime()), 
          ZoneId.systemDefault()));
    }
}

每次执行时,任务都会打印其调度时间,我们使用TimerTask#scheduledExecutionTime()方法收集这些时间。那么,如果我们想在固定延迟模式下每秒钟安排一次这个任务呢?我们必须使用前面提到的schedule()的重载版本:

new Timer().schedule(new NewsletterTask(), 0, 1000);

for (int i = 0; i < 3; i++) {
    Thread.sleep(1000);
}

当然,我们只对少数情况进行测试:

Email sent at: 2020-01-01T10:50:30.860
Email sent at: 2020-01-01T10:50:31.860
Email sent at: 2020-01-01T10:50:32.861
Email sent at: 2020-01-01T10:50:33.861

如上所示,每次执行之间至少有一秒钟的间隔,但有时会延迟一毫秒。这种现象是由于我们决定使用固定延迟重复。

3.3 调度一个每日任务

@Test
public void givenUsingTimer_whenSchedulingDailyTask_thenCorrect() {
    TimerTask repeatedTask = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    
    long delay = 1000L;
    long period = 1000L * 60L * 60L * 24L;
    timer.scheduleAtFixedRate(repeatedTask, delay, period);
}

4.取消调度器和任务

4.1 在Run方法中去掉调度任务

在run()方法对TimerTask本身的实现中调用TimerTask.cancel()方法:

@Test
public void givenUsingTimer_whenCancelingTimerTask_thenCorrect()
  throws InterruptedException {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
            cancel();
        }
    };
    Timer timer = new Timer("Timer");
    
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    
    Thread.sleep(1000L * 2);
}

4.2 取消定时器

调用Timer.cancel()方法:

@Test
public void givenUsingTimer_whenCancelingTimer_thenCorrect() 
  throws InterruptedException {
    TimerTask task = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    Timer timer = new Timer("Timer");
    
    timer.scheduleAtFixedRate(task, 1000L, 1000L);
    
    Thread.sleep(1000L * 2); 
    timer.cancel(); 
}

5.Timer对比ExecutorService

我们也可以使用ExecutorService来安排定时器任务,而不是使用定时器。下面是一个在指定间隔运行重复任务的快速示例:

@Test
public void givenUsingExecutorService_whenSchedulingRepeatedTask_thenCorrect() 
  throws InterruptedException {
    TimerTask repeatedTask = new TimerTask() {
        public void run() {
            System.out.println("Task performed on " + new Date());
        }
    };
    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    long delay  = 1000L;
    long period = 1000L;
    executor.scheduleAtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS);
    Thread.sleep(delay + period * 3);
    executor.shutdown();
}

那么定时器和ExecutorService解决方案之间的主要区别是什么:

  • 定时器对系统时钟的变化敏感;ScheduledThreadPoolExecutor并不会。
  • 定时器只有一个执行线程;ScheduledThreadPoolExecutor可以配置任意数量的线程。
  • TimerTask中抛出的运行时异常会杀死线程,因此后续的计划任务不会继续运行;使用ScheduledThreadExecutor–当前任务将被取消,但其余任务将继续运行。

6.本文代码

Java定时器样例代码

<think>首先,用户的问题是:“我想了解Java中的定时器Timer的用法或解决相关问题请问JavaTimer类使用方法示例代码定时任务”。这很清晰:用户想学习JavaTimer类如何用于定时任务,包括方法、示例代码和相关问题。回顾系统级指令:-使用$...$或$$...$$格式内数学表达式(但这里可能不适用,因为是Java代码)。-使用中文回答。-生成相关问题。-引用的段落末尾添加引用标识。-回答结构清晰,逐步解决问题。-参考用户的引用:引用[1]到[4]提供了关于JavaTimer的信息,包括示例代码、优缺点等。用户的引用:-[1]:展示了Timer的基本使用方法:创建Timer,定义TimerTask子类,使用schedule方法安排任务。-[2]:类似,展示了使用TimerExample,包括延迟和执行间隔。-[3]:总结了Timer类的使用。-[4]:提到TimerTask抽象类实现Runnable,支持多线程任务。用户的真实需求是JavaTimer类的使用方法和示例代码。所以,我的回答应该基于这些引用,提供简洁、准确的指导。**回答结构:**1.**介绍Timer类**:简要解释TimerTimerTask的作用。2.**核心方法**:列出关键方法,如schedule和scheduleAtFixedRate。3.**示例代码**:提供简单明了的代码示例。4.**注意事项**:讨论Timer的优缺点,如单线程问题、资源消耗。5.**生成相关问题**:基于主题生成相关问题。6.**引用标识**:末尾添加引用标识如[^1],指向用户提供的引用。确保回答是中文的。**关键内容提取:**-Timer类用于安排任务在指定时间后执行或定期执行。-TimerTask是抽象类,继承Runnable,定义任务逻辑。-方法:timer.schedule(task,delay,period)用于延迟后定期执行。-示例:如引用[1]和[2]所示。-优点:简单易用(引用[2])。-缺点:Timer是单线程的,如果一个任务延迟,会影响后续任务;可能导致资源泄露(引用[4]提到“消耗资源”)。**编写回答大纲:**1.**开头:**回应用户需求,介绍主题。2.**主要部分:**-Timer类概述。-关键方法解释。-步骤指南:如何创建定时任务。-完整示例代码。-注意事项。3.**结尾:**生成相关问题。**生成相关问题:**需要创建几个相关问题,例如:-Timer的线程安全问题?-替代方案如ScheduledExecutorService?-如何处理TimerTask的异常?-Timer的cancellation方法?-实际应用场景?**引用标识:**在回答中,如果直接引用内容,末尾添加[^数字],但用户说“上述引用仅供参考”,所以我不能直接复制,但基于它们构建回答。结束时加上引用。现在,草拟回答:</think> ### JavaTimer类使用详解 Java的`java.util.Timer`类和`java.util.TimerTask`是实现定时任务的简单方案。Timer类允许您安排任务在指定延迟后执行一次或定期重复执行,而TimerTask是一个抽象类,实现了Runnable接口,用于定义具体任务逻辑。下面我将基于用户提供的引用内容[^1][^2][^3][^4],逐步解释其用法,包括核心方法、示例代码和注意事项。整个讲解结构清晰,确保内容真实可靠。 #### 1. Timer类和TimerTask的基本概念 - **Timer类**:用于创建定时器,通过其schedule或scheduleAtFixedRate方法安排任务执行。 - **TimerTask类**:一个抽象类,需要您覆盖其run()方法定义任务逻辑(TimerTask实现Runnable接口)。 - **线程模型**:Timer使用单一后台线程(由Timer对象维护)执行所有安排的任务。这意味着如果多个任务被安排,它们会按顺序执行;如果某个任务运行时间过长,会影响后续任务的调度[^4]。 #### 2. 核心方法说明 Timer类的关键方法如下: - **schedule(TimerTask task, long delay)**:安排任务在指定`delay`毫秒后执行一次。 - **schedule(TimerTask task, Date time)**:安排任务在特定时间`time`执行一次。 - **schedule(TimerTask task, long delay, long period)**:安排任务在`delay`毫秒后执行,然后每隔`period`毫秒重复执行(任务执行时间可能因前一个任务延迟而漂移)。 - **scheduleAtFixedRate(TimerTask task, long delay, long period)**:类似schedule,但更强调固定速率:从第一次执行后,每隔`period`毫秒执行,无论前一个任务是否延迟(更适合周期稳定性要求高的场景)[^2][^3]。 #### 3. 使用步骤和示例代码 以下是创建一个简单定时任务的完整步骤和代码示例。基于引用[^1][^2],我将提供详细的Java代码。 **步骤:** 1. **定义TimerTask子类**:创建一个继承TimerTask的类,覆盖run()方法编写任务逻辑。 2. **实例化Timer**:创建一个Timer对象。 3. **安排任务**:使用Timer的schedule或scheduleAtFixedRate方法指定延迟和间隔。 4. **处理异常和取消**:注意添加异常处理,避免任务错误导致Timer崩溃。 **完整示例代码:** ```java import java.util.Timer; import java.util.TimerTask; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class TimerExample { // 定义时间格式化工具 private static String getCurrentTime() { return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } public static void main(String[] args) { // 1. 创建Timer实例 Timer timer = new Timer(); // 2. 创建TimerTask任务(使用匿名类方式) TimerTask task = new TimerTask() { @Override public void run() { try { // 任务逻辑:打印当前时间 System.out.println(getCurrentTime() + " - 定时任务已执行!"); // 假设这里可以是其他代码,如文件清理或数据同步 } catch (Exception e) { System.err.println(getCurrentTime() + " - 任务出错: " + e.getMessage()); } } }; // 3. 安排任务:延迟1秒后执行,然后每2秒重复执行 System.out.println(getCurrentTime() + " - Timer启动"); timer.schedule(task, 1000, 2000); // 参数:任务对象,延迟(毫秒),间隔(毫秒) // 可选:如果需要固定速率,使用scheduleAtFixedRate // timer.scheduleAtFixedRate(task, 1000, 2000); // 4. 示例:运行一段时间后取消定时器(防止程序无限运行) try { Thread.sleep(10000); // 让主线程休眠10秒 } catch (InterruptedException e) { e.printStackTrace(); } timer.cancel(); // 取消Timer所有任务 System.out.println(getCurrentTime() + " - Timer已取消"); } } ``` **代码说明:** - 本示例使用匿名类定义TimerTask,run()方法内打印当前时间,模拟定时执行的任务。 - `timer.schedule(task, 1000, 2000)`表示:延迟1000毫秒(1秒)后首次执行,然后每2000毫秒(2秒)重复执行。 - 添加了异常处理和Timer的取消逻辑,以防止资源泄露。 - 您可以根据需求选择schedule或scheduleAtFixedRate方法(引用[2]中讨论了二者的区别)[^1][^2]。 #### 4. 注意事项和优缺点 - **优点**: - **简单易用**:无需额外库,Java标准库自带,适合轻量级定时需求[^2]。 - **轻量线程**:TimerTask通过单一后台线程执行,减少了多线程复杂性(如果任务简单)。 - **缺点和问题**: - **单线程风险**:Timer只有一个线程;如果一个任务阻塞或运行时间过长,其他任务会被延迟或积压(可能导致时间漂移或不及时执行)[^4]。 - **资源消耗**:Timer实例会创建守护线程,如果不主动取消(timer.cancel()),可能造成内存泄露或后台线程挂起[^4]。 - **异常处理**:TimerTask的run()方法中未捕获异常会导致Timer线程终止,所有任务停止;因此必须在run()中添加try-catch块[^3]。 - **替代方案**:对于高要求场景(如多任务并发),推荐使用`java.util.concurrent.ScheduledExecutorService`(它基于线程池,更灵活高效)[^4]。 - **实际建议**:适合用于简单的后台任务,如周期性日志打印、数据刷新或短时间运行的定时操作;避免用于关键系统定时[^3][^4]。 #### 5. 常见问题解决 - **如何取消任务?** 调用TimerTask.cancel()Timer.cancel()。 - **如何避免时间漂移?** 使用scheduleAtFixedRate而非schedule。 - **如何处理多任务?** Timer支持多个TimerTask,但需注意单线程限制;对于并行需求,改用ScheduledExecutorService。 Java Timer类是定时任务入门的好工具,但需谨慎使用以防潜在问题[^1][^2][^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值