How to Run Certain Task Every Day at a Particular Time Using Scheduledexecutorservice

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

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

  • Use date time class's i.e. LocalDateTime of this new API
  • Use ZonedDateTime class to handle Time Zone specific calculation including Daylight Saving issues. You will find tutorial and example here.

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 initialDelay = duration.getSeconds();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(new MyRunnableTask(),
initialDelay,
TimeUnit.DAYS.toSeconds(1),
TimeUnit.SECONDS);

The initialDelay is 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.MILLISECONDS for handling the scheduling computaions in milliseconds.

And also TimerTask is better for this or ScheduledExecutorService?

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

From @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.

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

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:

  • MyTask is an interface with function execute.
  • While stopping ScheduledExecutorService, Always use awaitTermination after invoking shutdown on it: There's always a likelihood your task is stuck / deadlocking and the user would wait forever.

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

Use ScheduledExecutorService to run code at specific Date time only once

You can use ScheduledExecutorService.schedule(Runnable command, long delay, TimeUnit unit)

long delay = afterOneMinute.getTime() - System.currentTimeMillis();
ScheduledExecutorService executorService = ...;
executorService.schedule(runnable, delay, TimeUnit.MILLISECONDS);

Run JAVA program at exact times repeatedly using ScheduledExecutorService

I could not find a way to schedule a Timer with uneven intervals. However, it is relatively straightforward to schedule a Timer to execute at fixed intervals.

For your case, you could schedule three timers, one for :15, one for :25, and one for :45 past the hour:

public class TaskTest {
public static scheduleTask(int interval) {
GregorianCalendar cal = new GregorianCalendar();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int date = cal.get(Calendar.DATE);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int hourScheduled = hour;

// if we are past the scheduled time then schedule for the next hour
if (minute > interval) {
++hourScheduled;
}
cal.set(year, month, date, hourScheduled, interval);
long initialDelay = cal.getTimeInMillis() - System.currentTimeMillis();
if (initialDelay < 0) {
initialDelay = 0L;
}
// schedule each job for once per hour
int period = 60*60*1000;

Timer timer = new Timer();
SomeTask someTask = new SomeTask();
timer.scheduleAtFixedRate(someTask, initialDelay, period);
}

public static void main(String[] args) {
// schedule for the 15th, 25th and 45th min of time every hour
scheduleTask(15);
scheduleTask(25);
scheduleTask(45);
}
}

public class SomeTask extends TimerTask {
@Override
public void run() {
// do something
}
}

Schedule monthly task using ScheduledExecutorService

If you’re running in a Java EE environment, you should use a TimerService or the @Schedule annotation. But since you’re discussing ScheduledExecutorService, whose use is not permitted in a Java EE container, I’ll assume you are not running in one.

When using a ScheduledExecutorService, you can have the task itself schedule the next iteration:

final ScheduledExecutorService executor = /* ... */ ;

Runnable task = new Runnable() {
@Override
public void run() {
ZonedDateTime now = ZonedDateTime.now();
long delay = now.until(now.plusMonths(1), ChronoUnit.MILLIS);

try {
// ...
} finally {
executor.schedule(this, delay, TimeUnit.MILLISECONDS);
}
}
};

int dayOfMonth = 5;

ZonedDateTime dateTime = ZonedDateTime.now();
if (dateTime.getDayOfMonth() >= dayOfMonth) {
dateTime = dateTime.plusMonths(1);
}
dateTime = dateTime.withDayOfMonth(dayOfMonth);
executor.schedule(task,
ZonedDateTime.now().until(dateTime, ChronoUnit.MILLIS),
TimeUnit.MILLISECONDS);

In versions of Java earlier than 8, you can use a Calendar to do the same thing:

final ScheduledExecutorService executor = /* ... */ ;

Runnable task = new Runnable() {
@Override
public void run() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, 1);
long delay =
calendar.getTimeInMillis() - System.currentTimeMillis();

try {
// ...
} finally {
executor.schedule(this, delay, TimeUnit.MILLISECONDS);
}
}
};

int dayOfMonth = 5;

Calendar calendar = Calendar.getInstance();
if (calendar.get(Calendar.DAY_OF_MONTH) >= dayOfMonth) {
calendar.add(Calendar.MONTH, 1);
}
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
executor.schedule(task,
calendar.getTimeInMillis() - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);

How do I schedule a task to run once?

While the java.util.Timer used to be a good way to schedule future tasks, it is now preferable1 to instead use the classes in the java.util.concurrent package.

There is a ScheduledExecutorService that is designed specifically to run a command after a delay (or to execute them periodically, but that's not relevant to this question).

It has a schedule(Runnable, long, TimeUnit) method that

Creates and executes a one-shot action that becomes enabled after the given delay.


Using a ScheduledExecutorService you could re-write your program like this:

import java.util.concurrent.*;

public class Scratch {
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
public static void main(String[] args) {
System.out.println("Starting one-minute countdown now...");
ScheduledFuture<?> countdown = scheduler.schedule(new Runnable() {
@Override
public void run() {
// do the thing
System.out.println("Out of time!");
}}, 1, TimeUnit.MINUTES);

while (!countdown.isDone()) {
try {
Thread.sleep(1000);
System.out.println("do other stuff here");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
scheduler.shutdown();
}
}

One of the nice things you get by doing things this way is the ScheduledFuture<?> object you get back from calling schedule().

This allows you to get rid of the extra boolean variable, and just check directly whether the job has run.

You can also cancel the scheduled task if you don't want to wait anymore by calling its cancel() method.


1See Java Timer vs ExecutorService? for reasons to avoid using a Timer in favor of an ExecutorService.



Related Topics



Leave a reply



Submit