With classes like java.until.Timer and java.until.TimerTask, called Java Timer framework, you can set up the scheduling of simple tasks in J2ME. Before that, the developers had to write their own scheduler with object.wait() method.
Today, we will understand how to schedule recurring tasks in Java. Timer and TimerTask frameworks can be used to allow flexible scheduling. It is easy to learn and consists of two classes.
Before we start scheduling a recurring task, let’s understand the one-shot task first.
Here, we first need to look at framework classes since the scheduling framework is built on top of these framework classes. Let’s understand how a scheduling framework is used and implemented with an example of a simple timer for boiling water.
Imagine a timer that tells you how many minutes your water has been eating and plays a sound when it reaches a certain number. Here is how you code it:
package org.tiling.scheduling.example; import java.util.Timer; import java.util.TimerTask; public class WaterTimer { private final Timer timer = new Timer(); private final int minutes; public WaterTimer(int minutes) { this.minutes = minutes; } public void start() { timer.schedule(new TimerTask() { public void run() { playSound(); timer.cancel(); } private void playSound() { System.out.println("Your water is boiling!"); // Start a new thread to play a sound... } }, minutes ∗ 60 ∗ 1000); } public static void main(String[] args) { WaterTimer waterTimer = new waterTimer(2); waterTimer.start(); } } |
Here, the WaterTimer instance owns the Timer instance that will provide necessary scheduling. When the timer is started with start(), the TimerTask will be scheduled to execute after a specific time. When it reaches that time, run() will be called to TimerTask by Timer and it will play the sound. The application will be terminated after the timer is canceled.
‘Timer’ will allow the tasks to be scheduled for repeated execution by defining a fixed rate or delay between two recurrences. Your application may have a little more scheduling requirements than that, though.
Let’s take an alarm clock as an example. If a country or timezone uses daylight saving time, you cannot schedule the Timer for 86400000 milliseconds (24 hours) as it will make your alarm too late or too early when the Daylight saving time will be in effect.
Here, you will have to use calendar arithmetic to set the next occurrence for your alarm to go off. Here, we will use the AlarmClock class. You can easily find the source code for the same online.
package org.tiling.scheduling.example; import java.text.SimpleDateFormat; import java.util.Date; import org.tiling.scheduling.Scheduler; import org.tiling.scheduling.SchedulerTask; import org.tiling.scheduling.example.iterators.DailyIterator; public class AlarmClock { private final Scheduler scheduler = new Scheduler(); private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss.SSS"); private final int hourOfDay, minute, second; public AlarmClock(int hourOfDay, int minute, int second) { this.hourOfDay = hourOfDay; this.minute = minute; this.second = second; } public void start() { scheduler.schedule(new SchedulerTask() { public void run() { soundAlarm(); } private void soundAlarm() { System.out.println("Time to wake up, " + "It's " + dateFormat.format(new Date())); // Start a new thread to sound an alarm... } }, new DailyIterator(hourOfDay, minute, second)); } public static void main(String[] args) { AlarmClock alarmClock = new AlarmClock(7, 0, 0); alarmClock.start(); } } |
The code is pretty similar to our Water timer applications. The AlarmClock owns the Scheduler instance to provide the scheduling. With start(), SchedulerTask is scheduled by the alarm clock to play the alarm.
Instead of using the delay of 24 hours between alarms (because of our daylight saving time issue), we have used DailyIterator to define the schedule. This means the task will be executed at 7 in the morning every day.
Time to wake up, It's 01 Dec 2022 07:00:00.003 Time to wake up, It's 02 Dec 2022 07:00:00.002 Time to wake up, It's 03 Dec 2022 07:00:00.027 Time to wake up, It's 04 Dec 2022 07:00:00.016 … |
The interface ScheduleIterator is implemented by DailyIterator. It specifies the scheduled time of execution of SchedulerTask as a series of java.until.date objects. next() will iterate Date objects in chronological order and the return value of null will cancel the task. A ‘reschedule’ attempt will show an exception. Here is the ScheduleIterator interface:
package org.tiling.scheduling; import java.until.Date; public interface ScheduleIterator { public Date next(); } |
The next() method of DailyIterator will return a Date object at the same time every day. If you call next() on a newly constructed DailyIterator, you will get 7:00 AM on subsequent days, forever.
Here, DailyIterator is using java.until.Calendar instance. The next() will return the correct date by adding a day to the calendar. The ‘Calendar’ implementation takes care of daylight saving time, and it doesn’t need to be added by the developer.
package org.tiling.scheduling.examples.iterators; import org.tiling.scheduling.ScheduleIterator; import java.util.Calendar; import java.util.Date; /∗∗ ∗ A DailyIterator class returns a sequence of dates on subsequent days ∗ representing the same time each day. ∗/ public class DailyIterator implements ScheduleIterator { private final int hourOfDay, minute, second; private final Calendar calendar = Calendar.getInstance(); public DailyIterator(int hourOfDay, int minute, int second) { this(hourOfDay, minute, second, new Date()); } public DailyIterator(int hourOfDay, int minute, int second, Date date) { this.hourOfDay = hourOfDay; this.minute = minute; this.second = second; calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, hourOfDay); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, second); calendar.set(Calendar.MILLISECOND, 0); if (!calendar.getTime().before(date)) { calendar.add(Calendar.DATE, ‑1); } } public Date next() { calendar.add(Calendar.DATE, 1); return calendar.getTime(); } } |
Scheduler and SchedulerTask are the two other classes apart from ScheduleIterator to make up the framework. They use Timer and TimerTasks, counting schedules as a series of one-shot timers.
Scheduler source code:
package org.tiling.scheduling; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class Scheduler { class SchedulerTimerTask extends TimerTask { private SchedulerTask schedulerTask; private ScheduleIterator iterator; public SchedulerTimerTask(SchedulerTask schedulerTask, ScheduleIterator iterator) { this.schedulerTask = schedulerTask; this.iterator = iterator; } public void run() { schedulerTask.run(); reschedule(schedulerTask, iterator); } } private final Timer timer = new Timer(); public Scheduler() { } public void cancel() { timer.cancel(); } public void schedule(SchedulerTask schedulerTask, ScheduleIterator iterator) { Date time = iterator.next(); if (time == null) { schedulerTask.cancel(); } else { synchronized(schedulerTask.lock) { if (schedulerTask.state != SchedulerTask.VIRGIN) { throw new IllegalStateException("Task already scheduled " + "or cancelled"); } schedulerTask.state = SchedulerTask.SCHEDULED; schedulerTask.timerTask = new SchedulerTimerTask(schedulerTask, iterator); timer.schedule(schedulerTask.timerTask, time); } } } private void reschedule(SchedulerTask schedulerTask, ScheduleIterator iterator) { Date time = iterator.next(); if (time == null) { schedulerTask.cancel(); } else { synchronized(schedulerTask.lock) { if (schedulerTask.state != SchedulerTask.CANCELLED) { schedulerTask.timerTask = new SchedulerTimerTask(schedulerTask, iterator); timer.schedule(schedulerTask.timerTask, time); } } } } } |
SchedulerTask source code:
package org.tiling.scheduling; import java.util.TimerTask; public abstract class SchedulerTask implements Runnable { final Object lock = new Object(); int state = VIRGIN; static final int VIRGIN = 0; static final int SCHEDULED = 1; static final int CANCELLED = 2; TimerTask timerTask; protected SchedulerTask() { } public abstract void run(); public boolean cancel() { synchronized(lock) { if (timerTask != null) { timerTask.cancel(); } boolean result = (state == SCHEDULED); state = CANCELLED; return result; } } public long scheduledExecutionTime() { synchronized(lock) { return timerTask == null ? 0 : timerTask.scheduledExecutionTime(); } } } |
Like our Water Timer, every instance of Scheduler owns an instance of Timer, which provides underlying scheduling. The single one-shot timer for the water timer is replaced by the ‘Scheduler’ that strings a chain of one-shot timers. This executes SchedulerTask at the time defined by the ScheduleIterator.
The first execution of SchedulerTask is discovered by calling next() on the interface ‘ScheduleIterator’. The scheduling takes place after calling a one-shot schedule() on the underlying Timer class. TimerTask is supplied to one-shot execution and the nested ScheduleTimerTask contains the task and the iterator.
The run() will be called at the allotted time on the nested class, where the packaged task and iterators are referenced to reschedule the next occurrence of the task.
Reschedule() is similar to schedule(), but it is private and there are different checks on SchedulerTask. The rescheduling will process the recurrence indefinitely. That will construct a new nested class instance for each scheduled execution until the scheduler or the task is canceled.
SchedulerTask is like TimerTask. When created, the state is VIRGIN (never been scheduled). It shifts to SCHEDULED states once scheduled and then to CANCELLED after it’s canceled.
The three ways to cancel scheduling tasks are as follows.
Calling the cancel() method on SchedulerTask. It is the same as cancel() on TimerTask the task will never run again, but it will run to completion if already running.
The second way is with ScheduleIterator to return null. This is a shortcut to the first way. The scheduler class will call cancel() on the SchedulerTask. This will help you control when the scheduling stops as it will stop the iterator rather than the task.
The third will be to cancel the whole Scheduler by calling its cancel() method. This will cancel all of the tasks of the scheduler and no more tasks are scheduled on it.
Here, we have discussed scheduling one-time and recurring tasks in Java applications and how to cancel them. It uses the Java Timer framework with the object.wait() method and Timer, scheduler, and schedulerTask classes to set up recurring tasks. Further, we have also discussed the three ways to use cancel() to cancel the scheduled task when you want to stop executing them, which is an essential aspect of Java development services.