如何使用ScheduledExecutorService在特定时间每天运行某些任务?

 狮子_贾寒璐_759 发布于 2023-02-12 18:54

我每天凌晨5点都在尝试完成某项任务.所以我决定使用ScheduledExecutorService这个,但到目前为止,我已经看到了一些示例,显示了如何每隔几分钟运行一次任务.

而且我无法找到任何显示如何在早上的特定时间(早上5点)每天运行任务的例子,同时也考虑夏令时的事实 -

以下是我的代码,每15分钟运行一次 -

public class ScheduledTaskExample {
    private final ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(1);

    public void startScheduleTask() {
    /**
    * not using the taskHandle returned here, but it can be used to cancel
    * the task, or check if it's done (for recurring tasks, that's not
    * going to be very useful)
    */
    final ScheduledFuture taskHandle = scheduler.scheduleAtFixedRate(
        new Runnable() {
            public void run() {
                try {
                    getDataFromDatabase();
                }catch(Exception ex) {
                    ex.printStackTrace(); //or loggger would be better
                }
            }
        }, 0, 15, TimeUnit.MINUTES);
    }

    private void getDataFromDatabase() {
        System.out.println("getting data...");
    }

    public static void main(String[] args) {
        ScheduledTaskExample ste = new ScheduledTaskExample();
        ste.startScheduleTask();
    }
}

有没有办法,我可以安排一项任务,每天早上5点运行,同时ScheduledExecutorService考虑夏令时的事实?

TimerTask就是这个或更好ScheduledExecutorService

5 个回答
  • 与目前的Java SE 8版本一样,它具有出色的日期时间API,java.time这种计算可以更轻松地完成,而不是使用 java.util.Calendarjava.util.Date.

    使用此新API的日期时间类即LocalDateTime

    使用ZonedDateTime类处理特定于时区的计算,包括夏令时问题.你会在这里找到教程和例子.

    现在作为使用您的用例计划任务的示例示例:

    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
    ZonedDateTime nextRun = now.withHour(5).withMinute(0).withSecond(0);
    if(now.compareTo(nextRun) > 0)
        nextRun = nextRun.plusDays(1);
    
    Duration duration = Duration.between(now, nextRun);
    long initalDelay = duration.getSeconds();
    
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
    scheduler.scheduleAtFixedRate(new MyRunnableTask(),
        initalDelay,
        TimeUnit.DAYS.toSeconds(1),
        TimeUnit.SECONDS);
    

    initalDelay计算问调度延迟在执行TimeUnit.SECONDS.对于此用例,单位毫秒及以下的时差问题似乎可以忽略不计.但是你仍然可以在几毫秒内使用duration.toMillis()TimeUnit.MILLISECONDS处理调度计算.

    而TimerTask对于这个还是ScheduledExecutorService更好?

    不: ScheduledExecutorService看似好过TimerTask.StackOverflow已经为您解答了.

    来自@PaddyD,

    如果您希望在当地正确的时间运行,您仍然需要每年重启两次.scheduleAtFixedRate不会削减它,除非你对全年相同的UTC时间感到满意.

    事实并且@PaddyD已经给出了一个解决方法(给他+1),我提供了一个带有Java8日期时间API的工作示例ScheduledExecutorService.使用守护程序线程很危险

    class MyTaskExecutor
    {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        MyTask myTask;
        volatile boolean isStopIssued;
    
        public MyTaskExecutor(MyTask myTask$) 
        {
            myTask = myTask$;
    
        }
    
        public void startExecutionAt(int targetHour, int targetMin, int targetSec)
        {
            Runnable taskWrapper = new Runnable(){
    
                @Override
                public void run() 
                {
                    myTask.execute();
                    startExecutionAt(targetHour, targetMin, targetSec);
                }
    
            };
            long delay = computeNextDelay(targetHour, targetMin, targetSec);
            executorService.schedule(taskWrapper, delay, TimeUnit.SECONDS);
        }
    
        private long computeNextDelay(int targetHour, int targetMin, int targetSec) 
        {
            LocalDateTime localNow = LocalDateTime.now();
            ZoneId currentZone = ZoneId.systemDefault();
            ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);
            ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec);
            if(zonedNow.compareTo(zonedNextTarget) > 0)
                zonedNextTarget = zonedNextTarget.plusDays(1);
    
            Duration duration = Duration.between(zonedNow, zonedNextTarget);
            return duration.getSeconds();
        }
    
        public void stop()
        {
            executorService.shutdown();
            try {
                executorService.awaitTermination(1, TimeUnit.DAYS);
            } catch (InterruptedException ex) {
                Logger.getLogger(MyTaskExecutor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
    

    注意:

    MyTask是一个功能的接口execute.

    在停止时ScheduledExecutorService,总是awaitTermination在调用shutdown它之后使用:总是有可能你的任务卡住/死锁,用户会永远等待.

    我之前用Calender给出的例子只是我提到的一个想法,我避免了精确的时间计算和夏令时问题.根据@PaddyD的抱怨更新了解决方案

    2023-02-12 18:56 回答
  • 如果您没有能够使用Java 8的奢侈品,以下将满足您的需求:

    public class DailyRunnerDaemon
    {
       private final Runnable dailyTask;
       private final int hour;
       private final int minute;
       private final int second;
       private final String runThreadName;
    
       public DailyRunnerDaemon(Calendar timeOfDay, Runnable dailyTask, String runThreadName)
       {
          this.dailyTask = dailyTask;
          this.hour = timeOfDay.get(Calendar.HOUR_OF_DAY);
          this.minute = timeOfDay.get(Calendar.MINUTE);
          this.second = timeOfDay.get(Calendar.SECOND);
          this.runThreadName = runThreadName;
       }
    
       public void start()
       {
          startTimer();
       }
    
       private void startTimer();
       {
          new Timer(runThreadName, true).schedule(new TimerTask()
          {
             @Override
             public void run()
             {
                dailyTask.run();
                startTimer();
             }
          }, getNextRunTime());
       }
    
    
       private Date getNextRunTime()
       {
          Calendar startTime = Calendar.getInstance();
          Calendar now = Calendar.getInstance();
          startTime.set(Calendar.HOUR_OF_DAY, hour);
          startTime.set(Calendar.MINUTE, minute);
          startTime.set(Calendar.SECOND, second);
          startTime.set(Calendar.MILLISECOND, 0);
    
          if(startTime.before(now) || startTime.equals(now))
          {
             startTime.add(Calendar.DATE, 1);
          }
    
          return startTime.getTime();
       }
    }
    

    它不需要任何外部库,并将占用夏令时.只需传递您想要将任务作为Calendar对象运行的时间,并将任务作为一个Runnable.例如:

    Calendar timeOfDay = Calendar.getInstance();
    timeOfDay.set(Calendar.HOUR_OF_DAY, 5);
    timeOfDay.set(Calendar.MINUTE, 0);
    timeOfDay.set(Calendar.SECOND, 0);
    
    new DailyRunnerDaemon(timeOfDay, new Runnable()
    {
       @Override
       public void run()
       {
          try
          {
            // call whatever your daily task is here
            doHousekeeping();
          }
          catch(Exception e)
          {
            logger.error("An error occurred performing daily housekeeping", e);
          }
       }
    }, "daily-housekeeping");
    

    注意,计时器任务在守护程序线程中运行,不建议执行任何IO.如果需要使用User线程,则需要添加另一个取消计时器的方法.

    如果必须使用a ScheduledExecutorService,只需将startTimer方法更改为以下内容:

    private void startTimer()
    {
       Executors.newSingleThreadExecutor().schedule(new Runnable()
       {
          Thread.currentThread().setName(runThreadName);
          dailyTask.run();
          startTimer();
       }, getNextRunTime().getTime() - System.currentTimeMillis(),
       TimeUnit.MILLISECONDS);
    }
    

    我不确定该行为,但您可能需要一个停止方法,shutdownNow如果您沿着ScheduledExecutorService路线走,则会调用,否则当您尝试停止时,您的应用程序可能会挂起.

    2023-02-12 18:56 回答
  • 您是否考虑过使用Quartz Scheduler之类的东西?这个库有一个机制,用于使用类似cron的表达式来调度每天在设定的时间段运行的任务(看看CronScheduleBuilder).

    一些示例代码(未测试):

    public class GetDatabaseJob implements InterruptableJob
    {
        public void execute(JobExecutionContext arg0) throws JobExecutionException
        {
            getFromDatabase();
        }
    }
    
    public class Example
    {
        public static void main(String[] args)
        {
            JobDetails job = JobBuilder.newJob(GetDatabaseJob.class);
    
            // Schedule to run at 5 AM every day
            ScheduleBuilder scheduleBuilder = 
                    CronScheduleBuilder.cronSchedule("0 0 5 * * ?");
            Trigger trigger = TriggerBuilder.newTrigger().
                    withSchedule(scheduleBuilder).build();
    
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.scheduleJob(job, trigger);
    
            scheduler.start();
        }
    }
    

    预先有一些工作,你可能需要重写你的工作执行代码,但它应该可以让你更好地控制你的工作运行方式.如果需要,也可以更容易地更改计划.

    2023-02-12 18:56 回答
  • 在Java 8中:

    scheduler = Executors.newScheduledThreadPool(1);
    
    //Change here for the hour you want ----------------------------------.at()       
    Long midnight=LocalDateTime.now().until(LocalDate.now().plusDays(1).atStartOfDay(), ChronoUnit.MINUTES);
    scheduler.scheduleAtFixedRate(this, midnight, 1440, TimeUnit.MINUTES);
    

    2023-02-12 18:56 回答
  • Java8:
    我的升级版本从顶部答案:

      修复Web应用程序服务器不想停止的情况,因为线程池具有空闲线程

      没有递归

      使用您的自定义本地时间运行任务,就我而言,它是白俄罗斯,明斯克


    /**
     * Execute {@link AppWork} once per day.
     * <p>
     * Created by aalexeenka on 29.12.2016.
     */
    public class OncePerDayAppWorkExecutor {
    
        private static final Logger LOG = AppLoggerFactory.getScheduleLog(OncePerDayAppWorkExecutor.class);
    
        private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    
        private final String name;
        private final AppWork appWork;
    
        private final int targetHour;
        private final int targetMin;
        private final int targetSec;
    
        private volatile boolean isBusy = false;
        private volatile ScheduledFuture<?> scheduledTask = null;
    
        private AtomicInteger completedTasks = new AtomicInteger(0);
    
        public OncePerDayAppWorkExecutor(
                String name,
                AppWork appWork,
                int targetHour,
                int targetMin,
                int targetSec
        ) {
            this.name = "Executor [" + name + "]";
            this.appWork = appWork;
    
            this.targetHour = targetHour;
            this.targetMin = targetMin;
            this.targetSec = targetSec;
        }
    
        public void start() {
            scheduleNextTask(doTaskWork());
        }
    
        private Runnable doTaskWork() {
            return () -> {
                LOG.info(name + " [" + completedTasks.get() + "] start: " + minskDateTime());
                try {
                    isBusy = true;
                    appWork.doWork();
                    LOG.info(name + " finish work in " + minskDateTime());
                } catch (Exception ex) {
                    LOG.error(name + " throw exception in " + minskDateTime(), ex);
                } finally {
                    isBusy = false;
                }
                scheduleNextTask(doTaskWork());
                LOG.info(name + " [" + completedTasks.get() + "] finish: " + minskDateTime());
                LOG.info(name + " completed tasks: " + completedTasks.incrementAndGet());
            };
        }
    
        private void scheduleNextTask(Runnable task) {
            LOG.info(name + " make schedule in " + minskDateTime());
            long delay = computeNextDelay(targetHour, targetMin, targetSec);
            LOG.info(name + " has delay in " + delay);
            scheduledTask = executorService.schedule(task, delay, TimeUnit.SECONDS);
        }
    
        private static long computeNextDelay(int targetHour, int targetMin, int targetSec) {
            ZonedDateTime zonedNow = minskDateTime();
            ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec).withNano(0);
    
            if (zonedNow.compareTo(zonedNextTarget) > 0) {
                zonedNextTarget = zonedNextTarget.plusDays(1);
            }
    
            Duration duration = Duration.between(zonedNow, zonedNextTarget);
            return duration.getSeconds();
        }
    
        public static ZonedDateTime minskDateTime() {
            return ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
        }
    
        public void stop() {
            LOG.info(name + " is stopping.");
            if (scheduledTask != null) {
                scheduledTask.cancel(false);
            }
            executorService.shutdown();
            LOG.info(name + " stopped.");
            try {
                LOG.info(name + " awaitTermination, start: isBusy [ " + isBusy + "]");
                // wait one minute to termination if busy
                if (isBusy) {
                    executorService.awaitTermination(1, TimeUnit.MINUTES);
                }
            } catch (InterruptedException ex) {
                LOG.error(name + " awaitTermination exception", ex);
            } finally {
                LOG.info(name + " awaitTermination, finish");
            }
        }
    
    }
    

    2023-02-12 18:56 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有