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
相关产品推荐
相关产品推荐

