You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java定时执行JAR:定点触发任务的类库选型与阻塞非阻塞咨询

Hi David,

针对你需要维护时间列表并在指定时间触发独立JAR启动的需求,我整理了几个常用Java定时类库的选型建议,同时明确它们的阻塞/非阻塞特性,帮你快速做决策:

1. JDK 原生 Timer/TimerTask(阻塞式)
  • 特性:这是最基础的JDK自带定时工具,阻塞式——它依赖单个后台线程执行所有任务,如果某个任务执行时间过长,会直接阻塞后续所有任务的调度。
  • 适用场景:仅适合简单的定时需求,且任务执行时间短、不会互相干扰的场景。
  • 简单示例:
import java.util.Timer;
import java.util.TimerTask;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class TimerExample {
    public static void main(String[] args) {
        Timer timer = new Timer();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mma");
        
        // 模拟你的时间列表
        String[] timeList = {"8:47am", "8:49am", "9pm"};
        
        for (String timeStr : timeList) {
            LocalTime targetTime = LocalTime.parse(timeStr, formatter);
            long delay = calculateDelay(targetTime);
            
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    // 启动独立JAR的逻辑
                    try {
                        new ProcessBuilder("java", "-jar", "your-app.jar").start();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }, delay);
        }
    }
    
    private static long calculateDelay(LocalTime targetTime) {
        LocalTime now = LocalTime.now();
        long secondsDiff = targetTime.toSecondOfDay() - now.toSecondOfDay();
        if (secondsDiff < 0) {
            secondsDiff += 24 * 3600; // 今天过了就顺延到明天执行
        }
        return secondsDiff * 1000;
    }
}
  • 注意:如果某个JAR启动任务卡住,后续所有定时任务都会被延迟,不推荐用于复杂或任务执行时间不可控的场景。
2. JDK ScheduledExecutorService(非阻塞式)
  • 特性:基于线程池实现的非阻塞式定时工具,每个任务可以由独立线程执行,不会因为单个任务阻塞影响其他任务的调度。
  • 适用场景:大多数通用定时需求,尤其是任务可能有较长执行时间或需要并行执行的情况。
  • 简单示例:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class ScheduledExecutorExample {
    public static void main(String[] args) {
        // 线程池大小可根据任务数量调整
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mma");
        
        String[] timeList = {"8:47am", "8:49am", "9pm"};
        
        for (String timeStr : timeList) {
            LocalTime targetTime = LocalTime.parse(timeStr, formatter);
            long delay = calculateDelay(targetTime);
            
            executor.schedule(() -> {
                try {
                    new ProcessBuilder("java", "-jar", "your-app.jar").start();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, delay, TimeUnit.MILLISECONDS);
        }
    }
    
    private static long calculateDelay(LocalTime targetTime) {
        LocalTime now = LocalTime.now();
        long secondsDiff = targetTime.toSecondOfDay() - now.toSecondOfDay();
        if (secondsDiff < 0) {
            secondsDiff += 24 * 3600;
        }
        return secondsDiff * 1000;
    }
}
  • 优势:比Timer更灵活,支持线程池,任务执行互不干扰,是JDK原生方案里更推荐的选择。
3. Quartz 调度框架(非阻塞式)
  • 特性:功能极其强大的开源调度框架,非阻塞式,支持复杂的调度规则(如cron表达式、重复执行、错过任务的补偿处理等),还支持持久化任务状态,重启后不会丢失任务。
  • 适用场景:复杂的定时调度需求,比如需要动态添加/删除任务、任务持久化、或者有复杂时间规则的场景。
  • 简单示例(核心代码):
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class QuartzExample {
    public static void main(String[] args) throws SchedulerException {
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.start();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mma");
        
        String[] timeList = {"8:47am", "8:49am", "9pm"};
        
        for (int i = 0; i < timeList.length; i++) {
            LocalTime targetTime = LocalTime.parse(timeList[i], formatter);
            Date triggerTime = convertLocalTimeToDate(targetTime);
            
            // 定义任务
            JobDetail job = JobBuilder.newJob(StartJarJob.class)
                    .withIdentity("job" + i, "group1")
                    .build();
            
            // 定义触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger" + i, "group1")
                    .startAt(triggerTime)
                    .build();
            
            scheduler.scheduleJob(job, trigger);
        }
    }
    
    public static class StartJarJob implements Job {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            try {
                new ProcessBuilder("java", "-jar", "your-app.jar").start();
            } catch (Exception e) {
                throw new JobExecutionException(e);
            }
        }
    }
    
    private static Date convertLocalTimeToDate(LocalTime targetTime) {
        // 转换LocalTime为当前日期的Date,实际使用时建议考虑时区
        java.util.Calendar cal = java.util.Calendar.getInstance();
        cal.set(java.util.Calendar.HOUR_OF_DAY, targetTime.getHour());
        cal.set(java.util.Calendar.MINUTE, targetTime.getMinute());
        cal.set(java.util.Calendar.SECOND, targetTime.getSecond());
        if (cal.getTime().before(new Date())) {
            cal.add(java.util.Calendar.DAY_OF_YEAR, 1);
        }
        return cal.getTime();
    }
}
  • 注意:需要引入Quartz的Maven/Gradle依赖,学习成本略高,但功能完全覆盖复杂场景。
4. Spring Task(非阻塞式,Spring生态适用)
  • 特性:Spring框架自带的定时模块,非阻塞式,配置简单,支持注解和XML两种配置方式,适合已经在使用Spring生态的项目。
  • 适用场景:Spring项目中的定时需求,快速实现简单到中等复杂度的调度。
  • 简单示例(注解方式):
    首先在配置类开启定时功能:
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
public class SchedulingConfig {
}

然后编写定时任务类:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Component
public class JarScheduler {
    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mma");
    
    // 这里可以动态维护时间列表,比如从数据库读取
    private String[] timeList = {"8:47am", "8:49am", "9pm"};
    
    // 每秒检查一次是否到达目标时间,也可以为每个时间单独配置cron表达式
    @Scheduled(fixedRate = 1000)
    public void checkAndStartJar() {
        LocalTime now = LocalTime.now();
        for (String timeStr : timeList) {
            LocalTime targetTime = LocalTime.parse(timeStr, formatter);
            if (now.getHour() == targetTime.getHour() && now.getMinute() == targetTime.getMinute() 
                && now.getSecond() == 0) {
                try {
                    new ProcessBuilder("java", "-jar", "your-app.jar").start();
                    // 可以标记该时间已执行,避免每秒重复触发
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 优势:无需额外引入依赖(Spring环境下),配置简单,和Spring生态集成性好。
选型总结
  • 如果需求简单,且任务执行时间短:可以用Timer/TimerTask,但更推荐ScheduledExecutorService(非阻塞,更可靠)。
  • 如果是Spring项目:优先选Spring Task,快速集成无额外成本。
  • 如果需求复杂(动态任务、持久化、复杂时间规则):选Quartz,功能全面能覆盖所有场景。

另外,关于启动独立JAR的逻辑,建议使用ProcessBuilder而不是Runtime.exec(),因为前者更灵活,可以设置环境变量、工作目录等,同时要注意处理进程的输入输出流,避免因为流阻塞导致进程挂起。

内容的提问来源于stack exchange,提问作者David Jones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:42:46