In this recipe, you implemented the MyScheduledTask class to implement a custom task that can be executed on a ScheduledThreadPoolExecutor executor. This class extends the FutureTask class and implements the RunnableScheduledFuture interface. It implements the RunnableScheduledFuture interface because all the tasks executed in a scheduled executor must implement this interface and extend the FutureTask class. This is because this class provides valid implementations of the methods declared in the RunnableScheduledFuture interface. All the interfaces and classes mentioned earlier are parameterized classes and they possess the type of data that will be returned by the tasks.
To use a MyScheduledTask task in a scheduled executor, you override the decorateTask() method in the MyScheduledThreadPoolExecutor class. This class extends the ScheduledThreadPoolExecutor executor, and the method provides a mechanism to convert the default scheduled tasks implemented by the ScheduledThreadPoolExecutor executor into MyScheduledTask tasks. So, when you implement your own version of scheduled tasks, you have to implement your own version of a scheduled executor.
The MyScheduledTask class can execute delayed and periodic tasks. You implemented two methods with all of the necessary logic to execute both kinds of tasks. They are the getDelay() and run() methods.
The getDelay() method is called by the scheduled executor to know whether it has to execute a task. The behavior of this method changes in delayed and periodic tasks. As mentioned earlier, the constructor of the MyScheduledClass class receives the original ScheduledRunnableFuture object that was going to execute the Runnable object and stores it as an attribute of the class to have access to its methods and data. When we execute a delayed task, the getDelay() method returns the delay of the original task; however, in the case of a periodic task, the getDelay() method returns the difference between the startDate attribute and the actual date.
The run() method is the one that executes the task. One particularity of periodic tasks is that you have to put the next execution of the task in the queue of the executor as a new task if you want the task to be executed again. So, if you're executing a periodic task, you establish the startDate attribute value and add it to the actual date and period of the execution of the task and store the task again in the queue of the executor. The startDate attribute stores the date when the next execution of the task will begin. Then, you execute the task using the runAndReset() method provided by the FutureTask class. In the case of delayed tasks, you don't have to put them in the queue of the executor because they can only be executed once.
Finally, you overrode the scheduleAtFixedRate() method in the MyScheduledThreadPoolExecutor class. We mentioned earlier that for periodic tasks, you establish the value of the startDate attribute using the period of the task, but you haven't initialized that period yet. You have to override this method that receives this period as a parameter; do this to pass it to the MyScheduledTask class so it can use it.
The example is complete with the Task class that implements the Runnable interface, and it is the task executed in the scheduled executor. The main class of the example creates a MyScheduledThreadPoolExecutor executor and sends the following two tasks to them:
- A delayed task, which is to be executed 1 second after the actual date
- A periodic task, which is to be executed for the first time a second after the actual date and then every 3 seconds
The following screenshot shows part of the execution of this example. You can check whether the two kinds of tasks are executed properly: