java - ScheduledExecutorService 在本地和测试服务器中工作,但在实时服务器中只运行一次

标签 java scheduling runnable executorservice scheduledexecutorservice

我有一个 ScheduledExecutorService 来安排一个任务在 tomcat 应用程序中每 12 小时运行一次。该任务调用 REST 端点并执行一些其他计算并返回结果。它在本地和测试服务器中运行得非常好,但是,它只运行一次并且永远不会在实时服务器中运行。所有异常都得到处理,任务不到 5 秒即可完成。我还检查了服务器中的所有属性,以确保时间属性不会被其他属性覆盖。为了在本地进行测试,我将延迟时间减少到每 10 分钟一次,这仍然显示相同的行为,因此我排除了超时问题。我看过其他类似的问题,但似乎都没有帮助解决这个问题。 ScheduledExecutorService only runs once , JAVA ScheduledExecutorService only runs once when calling a Task<V> , ScheduledExecutorService only loops once , ScheduledExecutorService - Task stops running , ScheduledExecutorService - Ignore already running runnable , ScheduledExecutorService schedule issue , Timer vs. ScheduledExecutorService scheduling

下面是我的代码:

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder({"name", "number"})
public class Company {

    @JsonProperty("name")
    private String name;
    @JsonProperty("number")
    private int number;

    public String getName() {
        return this.name;
    }

    public int getNumber() {
        return this.number;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(Company.class)
            .append("name", this.name)
            .append("number", this.number).toString();
    }
}

public class CompanyDifference {

    private String name;
    private int difference;

    public CompanyDifference(String name, int difference) {
        this.name = name;
        this.difference = difference;
    }

    public String getName() {
        return this.name;
    }

    public int getDifference() {
        return this.difference;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(CompanyDifference.class)
            .append("name", this.name)
            .append("difference", this.difference).toString();
    }
}

@Singleton
public class TaskRunner {
    public void doTask () {
        try {
            System.out.println("takes 2 sets of json data and returns the difference for each company");

            // takes 2 sets of json data and returns the difference for each company
            ObjectMapper mapper = new ObjectMapper();
            InputStream dataOne = Company.class.getResourceAsStream("/data.json");
            InputStream dataTwo = Company.class.getResourceAsStream("/data2.json");

            Company[] companyDataOne = mapper.readValue(dataOne, Company[].class);
            Company[] companyDataTwo = mapper.readValue(dataTwo, Company[].class);

            // Find the difference for each company and map company name to difference
            Map<String, Integer> mapDifferenceToCompany = new HashMap<>();

            for (int i = 0; i < companyDataOne.length; i++) {
                mapDifferenceToCompany.put(companyDataOne[i].getName(), Math.abs(companyDataOne[i].getNumber() - companyDataTwo[i].getNumber()));
            }

            mapDifferenceToCompany.forEach((key, value) -> System.out.println(String.valueOf(new CompanyDifference(key, value))));
        } catch (IOException e) {
            logger.info(String.format("Error: Failed to convert json to object with exception %s", e));
            throw new TaskSchedulerException("Failed to convert json to object with exception", e);
        } catch (Exception e) {
            logger.info(String.format("Error: Failed with exception %s", e));
            throw new TaskSchedulerException("Failed with exception", e);
        }
    }
}

@Singleton
public class TaskScheduler {

    private final Runnable runnable; 
    private final ScheduledExecutorService executorService;
    private static final Logger logger = LoggerFactory.getLogger(TaskScheduler.class);

    @Inject
    public TaskScheduler(TaskRunner taskRunner, int initialDelay, int period, String timeUnits) {
        this.executorService = Executors.newScheduledThreadPool(1);
        this.runnable = taskRunner::doTask;

        this.scheduledFuture = this.executorService.scheduleAtFixedRate(this.runnable, initialDelay, period,    
             TimeUnit.valueOf(timeUnits));
    }

    public static void main(String[] args) {
        TaskRunner taskRunner = new TaskRunner();
        new TaskScheduler(taskRunner, 1, 10, "MINUTES");
    }
}

任务在初始延迟 1 分钟后在启动时运行一次,但不会运行下一个计划任务。它尝试运行计划任务,但似乎没有完成任务,在检查实时服务器中的 ScheduledThreadPoolExecutor 属性后,我发现队列大小下降到 0(应该始终为 1),这意味着没有任务预定。

这表明当它在初始任务完成后尝试运行计划任务时,它要么删除计划任务,要么因为当前任务未完成而无法计划下一个任务。不会抛出任何错误或异常,因为所有异常都已在 doTask 方法中处理。调度程序在本地和测试服务器中按预期工作,这使得复制场景变得困难。

想知道实现是否遗漏了什么或者是什么导致它无法完成下一个计划任务以及是什么导致队列大小下降到 0。java 版本是否会对调度程序的行为产生任何影响或为什么在实际环境中会发生这种情况?有什么原因吗?

我创建了一个 REST 端点,以使用 ScheduledFuture 和 ScheduledThreadPoolExecutor 的属性监视引擎盖下发生的事情

ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) schedulerService;
this.queueSize = executor.getQueue().size();
this.remainingCapacity = executor.getQueue().remainingCapacity();
this.terminated = schedulerService.isTerminated();
this.shutdown = schedulerService.isShutdown();
this.taskCount = executor.getTaskCount();
this.activeTaskCount = executor.getActiveCount();
this.completedTaskCount = executor.getCompletedTaskCount();
this.keepAliveTime = executor.getKeepAliveTime(TimeUnit.SECONDS);
this.coreThreadTimeOut = executor.allowsCoreThreadTimeOut();
this.cancelled = scheduledFuture.isCancelled();
this.delay = scheduledFuture.getDelay(TimeUnit.MINUTES);

初始延迟后第一次运行的结果:从这里我们可以看到它完成了第一个任务,我确实从计算中得到了所需的结果。 taskCount(自启动以来的计划任务数)为 2,这意味着第 2 个任务已被计划,队列大小仍然为 1,这很好。

{
  "queueSize": 1,
  "remainingCapacity": 2147483647,
  "terminated": false,
  "shutdown": false,
  "taskCount": 2,
  "activeTaskCount": 0,
  "completedTaskCount": 1,
  "keepAliveTime": 0,
  "coreThreadTimeOut": false, 
  "periodic": true, 
  "cancelled": false
}

尝试第二次运行后的结果:这是卡住的地方。 completedTaskCount 是 2,但我不认为它实际上完成了任务,因为我没有从计算或任何日志中得到结果来表明它已经开始或完成了任务。 taskCount 应该上升到 3,但它停留在 2,队列大小现在为 0。

{
  "queueSize": 0,
  "remainingCapacity": 2147483647,
  "terminated": false,
  "shutdown": false,
  "taskCount": 2,
  "activeTaskCount": 0,
  "completedTaskCount": 2,
  "keepAliveTime": 0,
  "coreThreadTimeOut": false, 
  "periodic": true, 
  "cancelled": false
}

当我在本地和测试服务器上检查这些时,它工作正常并且 taskCount 按预期增加并且队列大小始终为 1,这是预期的。由此,我可以看出由于某种原因,该任务在第二次运行时卡住了并且没有完成,因此它没有安排下一个任务。

javadoc 说:“如果此任务的任何执行时间超过其周期,则后续执行可能会延迟开始,但不会同时执行。”我假设这就是下一个原因任务未安排。如果您能解释可能导致这种情况发生的原因,那就太好了

最佳答案

问题不在于实现,实现很好。经过多次调试,运行该应用程序的 VM 框似乎出现了故障。多次重启并重新部署应用服务后,恢复正常。 有关调试应用程序时采取的步骤,请参阅问题。

关于java - ScheduledExecutorService 在本地和测试服务器中工作,但在实时服务器中只运行一次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53416915/

相关文章:

c++ - 事件管理应用的最佳适配时间调度算法

java - listView 更新不适用于静态变量

android - 如何防止Runnable中的内存不足异常

java - 不停止广播接收器中的当前歌曲

java - PDFBox 2.0 RC3 -- 查找和替换文本

java - 如何在保持顺序的同时将 List<P> 中的元素分组到 Map<K, List<V>> 中?

algorithm - 协调排类与可用性

python - 类似 cron 的循环任务调度程序设计

Java 线程停止通知程序

java - 设计一个 Spring 批处理应用程序从不同的资源(平面文件)读取数据