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

SingleThreadScheduledExecutor定时任务逻辑异常排查与优化咨询

问题分析与解决方案

嘿,我一眼就揪出了你代码里的核心问题,咱们一步步拆解:

1. 致命的时间初始化漏洞

你在run()方法开头只创建了一次Calendar cal = Calendar.getInstance();,这个对象的时间会永远停留在程序启动的那一刻!之后每次scheduleAtFixedRate触发任务时,用的都是这个旧时间,这直接导致了你的两个bug:

  • 若7:00启动程序,cal的时间固定为7:00,哪怕到了20:30,判断时仍认为处于有效时段,所以会继续执行任务;
  • 若在无效时段(比如21:00)启动,cal的时间固定为21:00,哪怕之后到了第二天8:00,判断时还是用21:00的时间,所以一直记录错误日志,不会执行任务。

2. 其他次要问题

  • running变量完全没用:你设置了running=false,但scheduleAtFixedRate会持续每隔30分钟触发任务,这个变量根本不会影响调度逻辑;
  • 每次调用run()都会创建新的SingleThreadScheduledExecutor,如果多次调用这个方法,会造成线程泄漏;
  • Runtime.exec()没有处理进程的输入/输出流,可能导致子进程阻塞(比如当进程输出较多时)。

修正后的基础代码

先把核心问题修复,同时优化其他细节:

public final void startScheduler() { // 重命名方法更清晰,避免和Runnable的run混淆
    // 只创建一次调度器,避免重复创建导致资源泄漏
    if (exec != null && !exec.isShutdown()) {
        return;
    }
    exec = Executors.newSingleThreadScheduledExecutor();
    exec.scheduleAtFixedRate(new Runnable() {
        public void run() {
            // 每次执行任务时都重新获取当前时间!
            final Calendar cal = Calendar.getInstance();
            try {
                int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
                int hour = cal.get(Calendar.HOUR_OF_DAY);
                
                // 用Calendar常量替代魔法数字,可读性更高
                if (dayOfWeek == Calendar.SUNDAY || dayOfWeek == Calendar.SATURDAY) {
                    Globals.addMessage("Non-scheduled work day, please try again later.");
                    Globals.addMessage("Process aborted.");
                } else if (hour < 6 || hour >= 20) { // 改成>=20,避免20:00之后执行任务
                    Globals.addMessage("Non-scheduled work hours, please try again later.");
                    Globals.addMessage("Process aborted.");
                } else {
                    // 用ProcessBuilder替代Runtime.exec,更可控
                    ProcessBuilder pb = new ProcessBuilder(ServerVar.getVariable(ServerVars.PATH));
                    Process process = pb.start();
                    
                    // 处理子进程的输入输出,避免阻塞
                    new Thread(() -> {
                        try (InputStream in = process.getInputStream()) {
                            in.transferTo(System.out);
                        } catch (IOException e) {
                            Globals.addMessage(e.toString());
                        }
                    }).start();
                    
                    new Thread(() -> {
                        try (InputStream err = process.getErrorStream()) {
                            err.transferTo(System.err);
                        } catch (IOException e) {
                            Globals.addMessage(e.toString());
                        }
                    }).start();
                    
                    Globals.addMessage("Program is running.");
                }
            } catch (IOException e) {
                Globals.addMessage(e.toString());
            }
        }
    }, 0, 30, TimeUnit.MINUTES);
}

// 记得添加关闭调度器的方法,避免程序退出时资源泄漏
public void stopScheduler() {
    if (exec != null && !exec.isShutdown()) {
        exec.shutdown();
        try {
            if (!exec.awaitTermination(1, TimeUnit.MINUTES)) {
                exec.shutdownNow();
            }
        } catch (InterruptedException e) {
            exec.shutdownNow();
        }
    }
}

更高效的实现方式(Java 8+)

如果你的项目用的是Java 8或更高版本,推荐使用java.timeAPI替代老旧的Calendar,代码更简洁易读:

import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;

public class TaskScheduler {
    private ScheduledExecutorService exec;

    public final void startScheduler() {
        if (exec != null && !exec.isShutdown()) {
            return;
        }
        exec = Executors.newSingleThreadScheduledExecutor();
        exec.scheduleAtFixedRate(this::executeTask, 0, 30, TimeUnit.MINUTES);
    }

    private void executeTask() {
        DayOfWeek currentDay = DayOfWeek.now();
        LocalTime currentTime = LocalTime.now();
        
        // 判断是否为周末
        if (currentDay == DayOfWeek.SATURDAY || currentDay == DayOfWeek.SUNDAY) {
            logAbort("Non-scheduled work day");
            return;
        }
        
        // 判断是否在工作时段内
        LocalTime workStart = LocalTime.of(6, 0);
        LocalTime workEnd = LocalTime.of(20, 0);
        if (currentTime.isBefore(workStart) || currentTime.isAfter(workEnd)) {
            logAbort("Non-scheduled work hours");
            return;
        }
        
        try {
            ProcessBuilder pb = new ProcessBuilder(ServerVar.getVariable(ServerVars.PATH));
            Process process = pb.start();
            
            // 统一处理进程流
            handleProcessStream(process.getInputStream(), System.out);
            handleProcessStream(process.getErrorStream(), System.err);
            
            Globals.addMessage("Program is running.");
        } catch (IOException e) {
            Globals.addMessage(e.toString());
        }
    }

    private void logAbort(String reason) {
        Globals.addMessage(reason + ", please try again later.");
        Globals.addMessage("Process aborted.");
    }

    private void handleProcessStream(InputStream in, OutputStream out) {
        new Thread(() -> {
            try (in; out) { // try-with-resources自动关闭流
                in.transferTo(out);
            } catch (IOException e) {
                Globals.addMessage(e.toString());
            }
        }).start();
    }

    public void stopScheduler() {
        if (exec != null && !exec.isShutdown()) {
            exec.shutdown();
            try {
                if (!exec.awaitTermination(1, TimeUnit.MINUTES)) {
                    exec.shutdownNow();
                }
            } catch (InterruptedException e) {
                exec.shutdownNow();
            }
        }
    }
}

进阶建议:使用专业调度框架

如果你的调度需求更复杂(比如需要排除节假日、动态调整执行时间等),推荐使用Quartz或者Spring Scheduler,这些框架提供了更强大的调度能力,比如可以直接用 cron 表达式定义执行规则:

// Spring Scheduler 示例(需Spring环境)
@Scheduled(cron = "0 0/30 6-19 * * MON-FRI") // 工作日6:00-19:30,每30分钟执行一次
public void scheduledTask() {
    try {
        ProcessBuilder pb = new ProcessBuilder(ServerVar.getVariable(ServerVars.PATH));
        Process process = pb.start();
        // 处理进程流...
        Globals.addMessage("Program is running.");
    } catch (IOException e) {
        Globals.addMessage(e.toString());
    }
}

这个cron表达式0 0/30 6-19 * * MON-FRI直接定义了“工作日(周一到周五)的6点到19点59分,每30分钟执行一次”,完全不需要手动判断时间,简洁又可靠。

内容的提问来源于stack exchange,提问作者M. Winnicki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:14:15