【Java编程的逻辑】定时任务

Timer和TimerTask

基本用法

TimerTask表示一个定时任务,它是一个抽象类,实现了Runnable,具体的定时任务需要继承该类,实现run方法。
Timer是一个具体类,它负责定时任务的调度和执行,主要方法有:

// 在指定绝对时间time运行任务task
public void schedule(TimerTask task, Date time);
// 在当前时间延时delay毫秒后运行任务task
public void schedule(TimerTask task, long delay);

// 固定延时重复执行,第一次计划执行时间为firstTime,如果firstTime小于当前时间,则立即执行
// 后一次的计划执行时间为前一次“实际执行”时间加上period
public void schedule(TimerTask task, Date firstTime, long period);
// 固定延时重复执行,第一次计划执行时间为当前时间加上delay毫秒
public void schedule(TimerTask task, long delay, long period);

// 固定频率重复执行。第一次计划执行时间为firstTime,如果firstTime小于当前时间,则立即执行
// 后一次的计划执行时间为前一次“计划执行”时间加上period
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);
// 固定频率重复执行。第一次计划执行时间为当前时间加上delay毫秒
public void scheduleAtFixedRate(TimerTask task, long delay, long period);

固定延时和固定频率的区别:二者都是重复执行,但后一次任务执行相对的时间是不一样的。 固定延时它是基于上一次任务的“实际”执行时间来算的,如果由于某种原因上一次执行时间延迟了,那么这次任务也延时。

基本示例

public class TimerFixedDelay {
    static class LongRunningTask extends TimerTask {
        @Override
        public void run() {
            try {
                System.out.println("start sleep");
                Thread.sleep(5000);
                System.out.println("sleep over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class FixedDelayTask extends TimerTask {
        @Override
        public void run() {
            System.out.println("FixedDelayTask:" + System.currentTimeMillis());
        }
    }
    static class FixedRateTask extends TimerTask {
        @Override
        public void run() {
            System.out.println("FixedRateTask:" + System.currentTimeMillis());
        }
    }
    public static void main(String[] args) {
        Timer timer = new Timer();
        // 延时10毫秒后执行,只执行一次,但耗时5秒
        timer.schedule(new LongRunningTask(), 10);
        // 延时100毫秒后执行,相对于上次实际执行时间,每隔2秒执行一次
        timer.schedule(new FixedDelayTask(), 100, 2000);
        // 延时100毫秒后执行,相对于上次计划执行时间,每隔1秒执行一次,
        timer.scheduleAtFixedRate(new FixedRateTask(), 100, 1000);
    }
}

通过这个示例发现第二和第三个任务只有在第一个任务运行结束后才会开始运行。
同时可以看出固定延时和固定频率的区别,第二个任务是固定延时,它是在第一个任务运行结束后才开始运行,2秒一次。第三个任务是固定频率,在第一个任务运行结束后才运行,但它会把之前没有运行的次数补过来,一下运行了5次。

基本原理

Timer内部主要由任务队列和Timer线程两部分组成。
任务队列是一个基于堆实现的优先级队列,按照下次执行的时间排优先级
Timer线程负责执行所有的定时任务,一个Timer对象只有一个Timer线程,所以任务会被延迟。

对于固定延时的任务,延时相对的是任务执行前的当前时间,而不是任务执行后。

对于固定频率的任务,延时相对的是最先的计划

死循环

一个Timer对象只有一个Timer线程,这意味着:定时任务不能耗时太长,更不能无限循环。

异常处理

在执行任何一个任务的run方法时,一旦run抛出异常,Timer线程就会退出,从而所有定时任务都会被取消

小结

  • 后台只有一个线程在运行
  • 固定频率的任务被延迟后,可能会立即执行多次,将次数不够
  • 固定延时任务的延时相对的是任务执行前的时间
  • 不要在定时任务中使用无限循环,会导致后面的任务无法执行
  • 一个定时任务的未处理异常会导致所有定时任务被取消
  • Timer 对提交的任务调度是基于绝对时间而不是相对时间的,所以通过其提交的任务对系统时钟的改变是敏感的(譬如提交延迟任务后修改了系统时间会影响其执行)

ScheduledExecutorService

基本用法

ScheduledExecutorService 是一个接口

public interface ScheduledExecutorService extends ExecutorService { 
    // 单次执行,在指定延时delay后运行command
    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
    // 单次执行,在指定延时delay后运行callable
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
    // 固定频率重复执行
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
    // 固定延时重复执行
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

返回类型ScheduledFuture,它是一个接口,扩展了Future和Delayed。
对于固定延时的任务,它是从任务执行后开始算的,第一次执行为当前时间 + initialDelay,第二次为第一次任务执行结束后再加上delay
对于固定频率的任务,第一次执行时间为当前时间 + initialDelay,第二次为当前时间 + initialDelay + period

ScheduledExecutorService的主要实现类是ScheduledTheradPoolExecutor,它是线程池ThreadPoolExecutor的子类。它的任务队列是一个无界的优先级队列,所以最大线程数对它没有作用。
工厂类Executors提供了一些创建ScheduledTheradPoolExecutor的方法

// 单线程的定时任务执行服务
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}
// 多线程的定时任务执行服务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

小结

  • 背后是线程池,可以有多个线程执行任务
  • 在任务执行后再设置下次执行时间,对于固定延时的任务更合理
  • 任务执行的线程会普通任务执行过程中的所有异常,一个定时任务的异常不会影响其他定时任务,不过,发生异常的任务(即使是一个重复任务)不会再被调度
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Spring Boot 中执行定时任务可以使用 Spring Framework 提供的 `@Scheduled` 注解来实现。以下是一些步骤: 1. 在你的 Spring Boot 应用程序中,创建一个带有 `@EnableScheduling` 注解的配置类。这个注解将启用 Spring 的定时任务功能。 ```java import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling public class AppConfig { } ``` 2. 创建一个带有定时任务方法的类,并使用 `@Scheduled` 注解来标记该方法。你可以设置定时任务执行时间表达式,决定任务在什么时候执行。 ```java import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class MyTask { @Scheduled(cron = "0 0 0 * * ?") // 每天凌晨执行 public void executeTask() { // 执行定时任务逻辑 } } ``` 在上面的例子中,定时任务 `executeTask` 方法使用了 `cron` 表达式来定义每天凌晨执行的时间表达式。 3. 确保你的定时任务类被正确扫描并注入到 Spring 容器中。你可以在应用程序的主类上添加 `@ComponentScan` 注解,以确保扫描到你的定时任务类。 ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @ComponentScan(basePackages = "com.example") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 在上面的例子中,`@ComponentScan` 注解将扫描 `com.example` 包及其子包中的组件。 现在,你的定时任务应该在指定的时间执行了。确保你的应用程序已经启动,并检查日志输出以确认定时任务执行情况。 这是一个简单的示例,你可以根据自己的需求调整定时任务执行时间和逻辑

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值