【问题标题】:Java schedule a job to run at certain time for maximum period of timeJava 安排作业在特定时间运行最长一段时间
【发布时间】:2022-02-05 13:14:11
【问题描述】:

我正在尝试安排一个应该每 10 秒运行一次的任务。然而,这个任务应该有一个动态的允许周期执行时间。换句话说,如果允许的最大时间为 5 秒,并且任务运行时间超过 5 秒,则它应该被杀死/关闭。

我曾尝试使用带有 cron 时间的 @Schedule,但无论我尝试什么,一旦它运行,我就无法杀死它。但是,有人建议我不要使用 @Schedule 并使用 ScheduledExecutorService 创建正常任务,但我不知道该怎么做。

我原来的方法是这样的:

    @Scheduled(cron = "0/10 * * * * ?")
    @Transactional
    public void testMethod(Integer period) {
        ThreadPoolTaskScheduler scheduler;

        scheduler.setAwaitTerminationSeconds(period);
        scheduler.setWaitForTasksToCompleteOnShutdown(false);

        importantMethod();
        
    }

我尝试过这样重写:

public void testMethod(){
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
  scheduler.scheduleAtFixedRate(importantMethod(), delay,
                                  period, TimeUnit.SECONDS);
}

但是,我不确定如何将其设置为每 10 秒或每 5 分钟运行一次无延迟,并且仅在超过允许的最大时间后才将其关闭。

任何帮助将不胜感激。

【问题讨论】:

    标签: java spring-boot scheduled-tasks


    【解决方案1】:

    我不确定这是否是最好的方法,但这是我想出的一种工作方式。

    您需要三种类型的任务:

    1. 必须定期运行的任务本身 - 重要任务
    2. 另一个将取消其执行的任务,从现在开始我将其称为 kill 任务。
    3. 提交要执行的重要任务的任务 - 提交任务,这是您将使用 ScheduledExecutorService 安排定期运行的任务

    这个想法是,当你的重要任务开始执行时,它会安排 kill 任务在 5 秒后运行。为了实现这一点,它需要知道它的Future 实例。为此,它需要提交任务来进行提交并让它知道它的'Future

    这里是important task

    import java.time.LocalTime;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.Future;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class ImportantTask implements Runnable {
    
        //that's just to make it easier to track what happens with each task
        private static long NEXT_ID = 1;
    
        //needed to schedule kill task
        private final ScheduledExecutorService executorService;
        private final long taskId;
        private Future<?> thisTaskFuture;
        //only needed to know when to cancel all tasks and end test
        private CountDownLatch countDownLatch;
    
        public ImportantTask(ScheduledExecutorService executorService) {
            this.executorService = executorService;
            this.taskId = NEXT_ID++;
        }
    
        @Override
        public void run() {
            //make half the tasks take too long
            long executionTimeSeconds = this.taskId % 2 == 0 ? 6 : 4;
            System.out.println(this.getLogMessage("started"));
    
            CancelFutureTask cancelFutureTask = new CancelFutureTask(this.thisTaskFuture);
            //this schedules the kill task for single execution, after 5 seconds delay
            this.executorService.schedule(cancelFutureTask, 5, TimeUnit.SECONDS);
            try {
                //simulating long execution
                Thread.sleep(executionTimeSeconds * 1_000);
            } catch (InterruptedException exc) {
                System.out.println(this.getLogMessage("interrupted"));
                //might need to check Thread.currentThread().isInterrupted(), depends on your exact case
                return;
            } finally {
                this.countDownLatch.countDown();
            }
            //the task is already finished, even if cancel is called, it has no effect
            System.out.println(this.getLogMessage("finished"));
        }
    
        private String getLogMessage(String taskStatus) {
            return String.format("Task with id - %d, %s at %s", this.taskId, taskStatus, LocalTime.now());
        }
    
        public void setThisTaskFuture(Future<?> thisTaskFuture) {
            this.thisTaskFuture = thisTaskFuture;
        }
    
        public void setCountDownLatch(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
    }
    

    CountDownLatch 在这里只是为了在执行几次后更容易停止 main 方法,您可能不需要它。注意代码中的cmets。

    kill task 很简单:

    import java.util.concurrent.Future;
    
    public class CancelFutureTask implements Runnable {
    
        private final Future<?> future;
        private final boolean mayInterrupt;
    
        public CancelFutureTask(Future<?> future, boolean mayInterrupt) {
            this.future = future;
            this.mayInterrupt = mayInterrupt;
        }
    
        public CancelFutureTask(Future<?> future) {
            this(future, true);
        }
    
        @Override
        public void run() {
            this.future.cancel(this.mayInterrupt);
        }
    }
    

    它只是取消了Future,如果未来已经完成执行,则不会有任何影响。

    还有submit task:

    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.Future;
    import java.util.concurrent.ScheduledExecutorService;
    
    public class SubmitNewImportantTask implements Runnable {
    
        private final ScheduledExecutorService executorService;
        private final CountDownLatch countDownLatch;
    
        public SubmitNewImportantTask(ScheduledExecutorService executorService, CountDownLatch countDownLatch) {
            this.executorService = executorService;
            this.countDownLatch = countDownLatch;
        }
    
        @Override
        public void run() {
            ImportantTask importantTask = new ImportantTask(this.executorService);
            //obtain Future instance
            Future<?> future = this.executorService.submit(importantTask);
            //let the task know of its Future instance
            importantTask.setThisTaskFuture(future);
            importantTask.setCountDownLatch(this.countDownLatch);
        }
    }
    

    提交important task执行,获取Future实例并设置在important task中。

    以及我用于测试的主要方法。

    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.ScheduledThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class FuturesMain {
    
        public static void main(String[] args) throws Exception {
            CountDownLatch count = new CountDownLatch(6);
            ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(10);
            SubmitNewImportantTask submitTask = new SubmitNewImportantTask(executorService, count);
            executorService.scheduleAtFixedRate(submitTask, 0, 10, TimeUnit.SECONDS);
            count.await();
            executorService.shutdown();
            boolean allTerminated = executorService.awaitTermination(10, TimeUnit.SECONDS);
            System.out.println("All terminated - " + allTerminated);
        }
    }
    

    【讨论】:

      【解决方案2】:

      根据描述,我的假设:

      1. 任务应每 10 秒运行一次
      2. 任务的最长超时时间为 5 秒
      3. 最大超时应该是可配置的

      application.properties 中的配置

      scheduler.timeout = 5000
      

      代码

      
          @Value("${scheduler.timeout}")
          private Long timeout;
          
          @Scheduled(cron = "0/10 * * * * ?")
          public void taskScheduler() {
              System.out.println("Task Started: " + LocalDateTime.now());
              this.executeTask((Callable<Object>) () -> {
                  TimeUnit.MILLISECONDS.sleep(3000);//Change values for testing: sleep time
                  System.out.println("Task Worked: " + LocalDateTime.now());
                  return true;
              }, timeout);
          }
          
          ExecutorService service = Executors.newFixedThreadPool(1);
          ScheduledExecutorService canceller = Executors.newSingleThreadScheduledExecutor();
          
          public <T> Future<T> executeTask(Callable<T> c, long timeoutMS) {
              final Future<T> future = service.submit(c);
              canceller.schedule(() -> {
                  if (!future.isDone())
                      System.out.println("Task Cancelled: " + LocalDateTime.now());
                  future.cancel(true);
                  return "done";
              }, timeoutMS, TimeUnit.MILLISECONDS);
              return future;
          }
      
      

      输出
      当任务在 3 秒内完成时(睡眠时间)

      Task Started: 2022-02-06T22:42:30.010704200
      Task Worked: 2022-02-06T22:42:33.014775100
      

      任务在 6 秒内完成时(睡眠时间)

      Task Started: 2022-02-06T22:47:30.003249500
      Task Cancelled: 2022-02-06T22:47:35.013195900
      

      【讨论】:

        猜你喜欢
        • 2019-08-01
        • 2013-09-08
        • 2019-10-01
        • 1970-01-01
        • 2016-06-18
        • 2019-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多