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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20387881/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 01:34:49  来源:igfitidea点击:

How to run certain task every day at a particular time using ScheduledExecutorService?

javatimertaskbackground-threadscheduledexecutorservice

提问by AKIWEB

I am trying to run a certain task everyday at 5 AM in the morning. So I decided to use ScheduledExecutorServicefor this but so far I have seen examples which shows how to run task every few minutes.

我正在尝试每天早上 5 点执行某项任务。所以我决定使用ScheduledExecutorService这个,但到目前为止我已经看到了一些例子,展示了如何每隔几分钟运行一次任务。

And I am not able to find any example which shows how to run a task every day at a particular time (5 AM) in the morning and also considering the fact of daylight saving time as well -

而且我找不到任何示例来说明如何在每天早上的特定时间(凌晨 5 点)运行任务,并且还考虑了夏令时这一事实-

Below is my code which will run every 15 minutes -

以下是我每 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();
    }
}

Is there any way, I can schedule a task to run every day 5 AM in the morning using ScheduledExecutorServiceconsidering the fact of daylight saving time as well?

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

And also TimerTaskis better for this or ScheduledExecutorService?

TimerTask更适合这个或ScheduledExecutorService

采纳答案by Sage

As with the present java SE 8 release with it's excellent date time API with java.timethese kind of calculation can be done more easily instead of using java.util.Calendarand java.util.Date.

与当前的 java SE 8 版本一样,它具有出色的日期时间 API,java.time可以更轻松地完成此类计算,而不是使用 java.util.Calendarand java.util.Date

Now as a sample example for scheduling a task with your use case:

现在作为使用用例安排任务的示例示例:

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);

The initalDelayis computed to ask the scheduler to delay the execution in TimeUnit.SECONDS. Time difference issues with unit milliseconds and below seems to be negligible for this use case. But you can still make use of duration.toMillis()and TimeUnit.MILLISECONDSfor handling the scheduling computaions in milliseconds.

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

And also TimerTask is better for this or ScheduledExecutorService?

而且 TimerTask 更适合这个或 ScheduledExecutorService?

NO:ScheduledExecutorServiceseemingly better than TimerTask. StackOverflow has already an answer for you.

NO:ScheduledExecutorService似乎比TimerTask. StackOverflow 已经为你解答了

From @PaddyD,

来自@PaddyD,

You still have the issue whereby you need to restart this twice a year if you want it to run at the right local time. scheduleAtFixedRate won't cut it unless you are happy with the same UTC time all year.

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

As it is true and @PaddyD already has given a workaround(+1 to him), I am providing a working example with Java8 date time API with ScheduledExecutorService. Using daemon thread is dangerous

确实如此,@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);
        }
    }
}

Note:

笔记:

  • MyTaskis an interface with function execute.
  • While stopping ScheduledExecutorService, Always use awaitTerminationafter invoking shutdownon it: There's always a likelihood your task is stuck / deadlocking and the user would wait forever.
  • MyTask是一个带有函数的接口execute
  • 在停止时ScheduledExecutorService,始终awaitTermination在调用后使用shutdown:您的任务总是有可能卡住/死锁,用户将永远等待。

The previous example I gave with Calender was just an ideawhich I did mention, I avoided exact time calculation and Daylight saving issues. Updated the solution on per the complain of @PaddyD

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

回答by lmika

Have you considered using something like Quartz Scheduler? This library has a mechanism for scheduling tasks to run at a set period of time every day using a cron like expression (take a look at CronScheduleBuilder).

您是否考虑过使用Quartz Scheduler 之类的东西?这个库有一种机制,可以使用类似 cron 的表达式(看一看CronScheduleBuilder)每天在固定的时间段内安排任务运行。

Some example code (not tested):

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

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();
    }
}

There's a bit more work upfront, and you may need to rewrite your job execution code, but it should give you more control over how you want you job to run. Also it would be easier to change the schedule should you need to.

有更多的前期工作,您可能需要重写您的作业执行代码,但它应该让您更好地控制您希望如何运行您的作业。如果需要,更改时间表也会更容易。

回答by user987339

I had a similar problem. I had to schedule bunch of tasks that should be executed during a day using ScheduledExecutorService. This was solved by one task starting at 3:30 AM scheduling all other tasks relatively to his current time. And rescheduling himself for the next day at 3:30 AM.

我有一个类似的问题。我不得不使用ScheduledExecutorService. 这是通过一项从凌晨 3:30 开始的任务相对于他的当前时间安排所有其他任务来解决的。并在第二天凌晨 3:30 重新安排自己。

With this scenario daylight savings are not an issue anymore.

在这种情况下,夏令时不再是问题。

回答by Victor

In Java 8:

在 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);

回答by PaddyD

If you don't have the luxury of being able to use Java 8, the following will do what you need:

如果您没有能力使用 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();
   }
}

It doesn't require any external libs, and will account for daylight savings. Simply pass in the time of day you want to run the task as a Calendarobject, and the task as a Runnable. For example:

它不需要任何外部库,并且会考虑夏令时。只需将要运行任务的时间作为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");

N.B. the timer task runs in a Daemon thread which is not recommended for doing any IO. If you need to use a User thread, you will need to add another method which cancels the timer.

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

If you have to use a ScheduledExecutorService, simply change the startTimermethod to the following:

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

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

I am not sure of the behaviour but you may need a stop method which calls shutdownNowif you go down the ScheduledExecutorServiceroute, otherwise your application may hang when you try to stop it.

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

回答by Alexey Alexeenka

Java8:
My upgrage version from top answer:

Java8:
我从顶级答案的升级版本:

  1. Fixed situation when Web Application Server doens't want to stop, because of threadpool with idle thread
  2. Without recursion
  3. Run task with your custom local time, in my case, it's Belarus, Minsk
  1. 修复了 Web 应用服务器不想停止的情况,因为线程池有空闲线程
  2. 没有递归
  3. 使用您自定义的本地时间运行任务,就我而言,它是白俄罗斯、明斯克



/**
 * 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");
        }
    }

}

回答by Luc Chevallier

You can use a simple date parse, if the time of the day is before now, let's start tomorrow :

您可以使用简单的日期解析,如果一天中的时间在现在之前,让我们明天开始:

  String timeToStart = "12:17:30";
  SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss");
  SimpleDateFormat formatOnlyDay = new SimpleDateFormat("yyyy-MM-dd");
  Date now = new Date();
  Date dateToStart = format.parse(formatOnlyDay.format(now) + " at " + timeToStart);
  long diff = dateToStart.getTime() - now.getTime();
  if (diff < 0) {
    // tomorrow
    Date tomorrow = new Date();
    Calendar c = Calendar.getInstance();
    c.setTime(tomorrow);
    c.add(Calendar.DATE, 1);
    tomorrow = c.getTime();
    dateToStart = format.parse(formatOnlyDay.format(tomorrow) + " at " + timeToStart);
    diff = dateToStart.getTime() - now.getTime();
  }

  ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
  scheduler.scheduleAtFixedRate(new MyRunnableTask(), TimeUnit.MILLISECONDS.toSeconds(diff) ,
                                  24*60*60, TimeUnit.SECONDS);

回答by Niby

Just to add up on Victor's answer.

只是为了补充维克多的回答

I would recommend to add a check to see, if the variable (in his case the long midnight) is higher than 1440. If it is, I would omit the .plusDays(1), otherwise the task will only run the day after tomorrow.

我建议添加一个检查以查看变量(在他的情况下是 long midnight)是否高于1440. 如果是,我会省略.plusDays(1),否则任务只会在后天运行。

I did it simply like this:

我只是这样做的:

Long time;

final Long tempTime = LocalDateTime.now().until(LocalDate.now().plusDays(1).atTime(7, 0), ChronoUnit.MINUTES);
if (tempTime > 1440) {
    time = LocalDateTime.now().until(LocalDate.now().atTime(7, 0), ChronoUnit.MINUTES);
} else {
    time = tempTime;
}

回答by Shaik Elias

The following example work for me

以下示例对我有用

public class DemoScheduler {

    public static void main(String[] args) {

        // Create a calendar instance
        Calendar calendar = Calendar.getInstance();

        // Set time of execution. Here, we have to run every day 4:20 PM; so,
        // setting all parameters.
        calendar.set(Calendar.HOUR, 8);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.AM_PM, Calendar.AM);

        Long currentTime = new Date().getTime();

        // Check if current time is greater than our calendar's time. If So,
        // then change date to one day plus. As the time already pass for
        // execution.
        if (calendar.getTime().getTime() < currentTime) {
            calendar.add(Calendar.DATE, 1);
        }

        // Calendar is scheduled for future; so, it's time is higher than
        // current time.
        long startScheduler = calendar.getTime().getTime() - currentTime;

        // Setting stop scheduler at 4:21 PM. Over here, we are using current
        // calendar's object; so, date and AM_PM is not needed to set
        calendar.set(Calendar.HOUR, 5);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.AM_PM, Calendar.PM);

        // Calculation stop scheduler
        long stopScheduler = calendar.getTime().getTime() - currentTime;

        // Executor is Runnable. The code which you want to run periodically.
        Runnable task = new Runnable() {

            @Override
            public void run() {

                System.out.println("test");
            }
        };

        // Get an instance of scheduler
        final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        // execute scheduler at fixed time.
        scheduler.scheduleAtFixedRate(task, startScheduler, stopScheduler, MILLISECONDS);
    }
}

reference: https://chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time/

参考:https: //chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time/

回答by Amol Suryawanshi

You can use below class to schedule your task every day particular time

您可以使用下面的课程来安排每天特定时间的任务

package interfaces;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class CronDemo implements Runnable{

    public static void main(String[] args) {

        Long delayTime;

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        final Long initialDelay = LocalDateTime.now().until(LocalDate.now().plusDays(1).atTime(12, 30), ChronoUnit.MINUTES);

        if (initialDelay > TimeUnit.DAYS.toMinutes(1)) {
            delayTime = LocalDateTime.now().until(LocalDate.now().atTime(12, 30), ChronoUnit.MINUTES);
        } else {
            delayTime = initialDelay;
        }

        scheduler.scheduleAtFixedRate(new CronDemo(), delayTime, TimeUnit.DAYS.toMinutes(1), TimeUnit.MINUTES);

    }

    @Override
    public void run() {
        System.out.println("I am your job executin at:" + new Date());
    }
}