Java中实现定时任务执行的几种常见方式总结

 更新时间:2025年09月02日 10:22:06   作者:inrgihc  
几乎在所有的项目中,定时任务的使用都是不可或缺的,如果使用不当甚至会造成资损,这篇文章主要总结介绍了Java中实现定时任务执行的几种常见方式,需要的朋友可以参考下

一、概述

关于在Java项目中实现定时任务执行的几种方式总结如下:

单进程实现定时任务主要有以下几种方式:

1. Timer 和 TimerTask(JDK 原生)

2. ScheduledExecutorService(JDK 1.5+,推荐)

3. Spring Task(Spring 框架支持)

4. Quartz(企业级任务调度框架)

5. DelayQueue 阻塞队列

分布式定时任务

1、使用Redis来实现定时任务

2、使用xxl-job实现定时任务

二、详情

1. Timer 和 TimerTask(JDK 原生)

Java中的Timer类是一个定时调度器,用于在指定的时间点执行任务。Timer 类用于实现定时任务,最大的好处就是他的实现非常简单,特别的轻量级,因为它是Java内置的,所以只需要简单调用就行了。

public class MyTimerTask {

    public static void main(String[] args) {
        // 定义一个任务
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask:" + new Date());
            }
        };
        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask, 1000, 3000);
    }
}
优点缺点
  • 简单易用,适合单线程任务调度。
  • 基于绝对时间(Date)执行任务。
  • Timer内部是单线程执行任务的,如果某个任务执行时间较长,会影响后续任务的执行
  • 如果任务抛出未捕获异常,将导致整个 Timer 线程终止,影响其他任务的执行
  • Timer 无法提供高精度的定时任务。因为系统调度和任务执行时间的不确定性,可能导致任务执行的时间不准确。
  • 虽然可以使用 cancel 方法取消任务,但这仅仅是将任务标记为取消状态,仍然会在任务队列中占用位置,无法释放资源。这可能导致内存泄漏。
  • 当有大量任务时,Timer 的性能可能受到影响,因为它在每次扫描任务队列时都要进行时间比较。
  • Timer执行任务完全基于JVM内存,一旦应用重启,那么队列中的任务就都没有了

2. ScheduledExecutorService(JDK 1.5+,推荐)

ScheduledExecutorService基于线程池(ThreadPoolExecutor),支持多任务并发执行。相比 Timer,更稳定、更灵活。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

// 延迟 1s 后执行
scheduler.schedule(() -> {
    System.out.println("Task executed once at: " + new Date());
}, 1, TimeUnit.SECONDS);

// 延迟 1s 后执行,之后每 2s 执行一次
scheduler.scheduleAtFixedRate(() -> {
    System.out.println("Fixed-rate task executed at: " + new Date());
}, 1, 2, TimeUnit.SECONDS);
优点缺点
  • 是 JDK 1.5 之后自带的 API,因此使用起来也比较方便
  • 执行任务时使用线程池管理,不会造成任务间的相互影响
  • 支持 scheduleAtFixedRate(固定频率)和 scheduleWithFixedDelay(固定间隔)。
  • 需要配置线程池
  • 如果任务抛出异常,后续任务 会被取消!需在任务内捕获异常。

3. Spring Task(Spring 框架支持)

适合 Spring/Spring Boot 项目,基于 ScheduledExecutorService,提供注解式任务调度。

@EnableScheduling
@SpringBootApplication
public class SpringbootApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

@Component
public class TimerTask {
 
    @Scheduled(cron = "0 0 0 0 7 *")
    public void task() {
        System.out.println("定时任务...");
    }
}
优点缺点
  • 注解驱动,开发简单。
  • 支持 Cron 表达式(如 0 0 12 * * ? 表示每天中午 12 点执行)。
只适合 Spring/Spring Boot 项目

4. Quartz(企业级任务调度框架)

Quartz是OpenSymphony开源组织在Job scheduling领域的一个Java实现的开源项目。

// 1. 定义 Job
public class MyJob implements Job {
    @Override
    public void execute(JobExecutionContext context) {
        System.out.println("Quartz Job executed at: " + new Date());
    }
}

// 2. 配置 Trigger 和 Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();

JobDetail job = JobBuilder.newJob(MyJob.class)
    .withIdentity("myJob", "group1")
    .build();

Trigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("myTrigger", "group1")
    .startNow()
    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
        .withIntervalInSeconds(5) // 每 5s 执行一次
        .repeatForever())
    .build();

scheduler.scheduleJob(job, trigger);
scheduler.start();
优点缺点
  • 支持数据库持久化,重启后可恢复调度
  • 支持动态调整任务(如暂停、恢复、修改执行时间)
  • 分布式需要使用数据库

5. DelayQueue 阻塞队列

DelayQueue是一个带有延迟时间的无界阻塞队列,它的元素必须实现Delayed接口。当从DelayQueue中取出一个元素时,如果其延迟时间还未到达,则会阻塞等待,直到延迟时间到达。因此,我们可以通过将任务封装成实现Delayed接口的元素,将其放入DelayQueue中,再使用一个线程不断地从DelayQueue中取出元素并执行任务,从而实现定时任务的调度。

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayedTask implements Delayed {
    private final String taskName;
    private final long executeTime; // 执行时间(毫秒时间戳)

    public DelayedTask(String taskName, long delayMs) {
        this.taskName = taskName;
        this.executeTime = System.currentTimeMillis() + delayMs;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        // 返回剩余延迟时间
        long remainingDelay = executeTime - System.currentTimeMillis();
        return unit.convert(remainingDelay, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed other) {
        // 按执行时间排序(早到期者优先)
        return Long.compare(this.executeTime, ((DelayedTask) other).executeTime);
    }

    public void execute() {
        System.out.println("Executing task: " + taskName + " at " + new Date(executeTime));
    }
}

public class DelayQueueScheduler {
    private static final DelayQueue<DelayedTask> queue = new DelayQueue<>();

    public static void main(String[] args) throws InterruptedException {
        // 提交延迟任务
        queue.put(new DelayedTask("Task 1", 3000)); // 3s 后执行
        queue.put(new DelayedTask("Task 2", 1000)); // 1s 后执行
        queue.put(new DelayedTask("Task 3", 5000)); // 5s 后执行

        // 启动消费者线程处理任务
        while (!queue.isEmpty()) {
            DelayedTask task = queue.take(); // 阻塞直到有任务到期
            task.execute();
        }
    }
}
优点缺点
  • 精确延迟、无第三方依赖
  • 只能单进程内使用

6、使用 Redis 实现延迟任务

使用 Redis 实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式。

① ZSet 实现方式

通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下:

import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;

public class DelayQueueExample {
    // zset key
    private static final String _KEY = "myTaskQueue";
    
    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        // 30s 后执行
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY, delayTime, "order_1");
        // 继续添加测试数据
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
        jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
        jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
        // 开启定时任务队列
        doDelayQueue(jedis);
    }

    /**
     * 定时任务队列消费
     * @param jedis Redis 客户端
     */
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {
        while (true) {
            // 当前时间
            Instant nowInstant = Instant.now();
            long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间
            long nowSecond = nowInstant.getEpochSecond();
            // 查询当前时间的所有任务
            Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
            for (String item : data) {
                // 消费任务
                System.out.println("消费:" + item);
            }
            // 删除已经执行的任务
            jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
            Thread.sleep(1000); // 每秒查询一次
        }
    }
}

② 键空间通知

我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。

默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启之后定时任务的代码如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;

public class TaskExample {
    public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        // 执行定时任务
        doTask(jedis);
    }

    /**
     * 订阅过期消息,执行定时任务
     * @param jedis Redis 客户端
     */
    public static void doTask(Jedis jedis) {
        // 订阅过期消息
        jedis.psubscribe(new JedisPubSub() {
            @Override
            public void onPMessage(String pattern, String channel, String message) {
                // 接收到消息,执行定时任务
                System.out.println("收到消息:" + message);
            }
        }, _TOPIC);
    }
}

7、使用xxl-job实现定时任务

xxl-job是一款分布式定时任务调度平台,可以实现各种类型的定时任务调度,如定时执行Java代码、调用HTTP接口、执行Shell脚本等。xxl-job采用分布式架构,支持集群部署,可以满足高并发、大数据量的任务调度需求。

三、开源项目

1、https://gitee.com/xuxueli0323/xxl-job

2、https://gitee.com/dromara/disjob

3、https://gitee.com/KFCFans/PowerJob

4、https://gitee.com/aizuda/snail-job

5、https://gitee.com/elasticjob/elastic-job

到此这篇关于Java中实现定时任务执行的几种常见方式总结的文章就介绍到这了,更多相关Java定时任务执行内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java String类型对象转换为自定义类型对象的实现

    java String类型对象转换为自定义类型对象的实现

    本文主要介绍了java String类型对象转换为自定义类型对象的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Java中异常Exception和捕获以及自定义异常详解

    Java中异常Exception和捕获以及自定义异常详解

    在工作过程中,我们常常需要在合适的地方抛出合适的异常,除了java自带的一些异常,我们可以在项目中定制自己的异常,并且全局捕获它,下面这篇文章主要给大家介绍了关于Java中异常Exception和捕获以及自定义异常的相关资料,需要的朋友可以参考下
    2023-05-05
  • 详解使用spring aop实现业务层mysql 读写分离

    详解使用spring aop实现业务层mysql 读写分离

    本篇文章主要介绍了使用spring aop实现业务层mysql 读写分离,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • Spring Hystrix熔断报警原理图例解析

    Spring Hystrix熔断报警原理图例解析

    这篇文章主要介绍了Spring Hystrix熔断报警原理图例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java利用 Exchanger 实现游戏中交换装备

    Java利用 Exchanger 实现游戏中交换装备

    JDK 1.5 开始 JUC 包下提供的 Exchanger 类可用于两个线程之间交换信息。下面我们就来看看Java是如何利用Exchanger一行代码实现游戏中交换装备的
    2021-09-09
  • Spring Boot解决项目启动时初始化资源的方法

    Spring Boot解决项目启动时初始化资源的方法

    这篇文章主要给大家介绍了关于Spring Boot如何解决项目启动时初始化资源的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-05-05
  • SpringBoot集成单点登录CAS的方法实现

    SpringBoot集成单点登录CAS的方法实现

    本文主要介绍了SpringBoot集成单点登录CAS的方法实现,包括CAS的基本概念、集成步骤、具体代码示例等,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03
  • 带你全面认识Java中的异常处理

    带你全面认识Java中的异常处理

    在你所写过的代码中,你已经接触过一些异常了,我们可以通过一些简单的代码让我们理解一些简单的异常,下面这篇文章主要给大家介绍了关于Java中异常处理的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • SpringBoot接口或方法进行失败重试的实现方式

    SpringBoot接口或方法进行失败重试的实现方式

    为了防止网络抖动,影响我们核心接口或方法的成功率,通常我们会对核心方法进行失败重试,如果我们自己通过for循环实现,会使代码显得比较臃肿,所以本文给大家介绍了SpringBoot接口或方法进行失败重试的实现方式,需要的朋友可以参考下
    2024-07-07
  • Idea中如何调出Run dashboard 或services窗口

    Idea中如何调出Run dashboard 或services窗口

    这篇文章主要介绍了Idea中如何调出Run dashboard 或services窗口问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03

最新评论