This is part 10 of this series. If you missed any of the last nine, you can find them here:
- Part 1 – Overview
- Part 2 – Lifecycle of threads
- Part 3 – Daemon threads
- Part 4 – Interrupting threads
- Part 5 – Thread synchronization
- Part 6 – Runnable vs Callable
- Part 7 – Executors and Executor Services
- Part 8 – TimeUnits and Futures
- Part 9 – Shutting down executors and scheduling regular tasks
Introduction
In the last post we learned how to gracefully shut down Executor
and ExecutorService
objects. We also looked at scheduling tasks to run regularly with the Timer
and TimerTask
classes.
In this post we’re also going to look at scheduling tasks to run regularly. However, we’ll be using the ScheduledThreadPoolExecutor
to do this.
Problems with Timers and TimerTasks
We saw that using the Timer
and TimerTask
classes was easy enough, but there were some problems.
The biggest issue was that we have to extend the TimerTask
class. TimerTask
is an abstract class, and not a functional interface. This means that we cannot use lambda expressions to create TimerTask
tasks. We must create tasks as top-level, named inner or anonymous inner classes.
Another (possibly less) concerning problem is that each Timer
object has only a single background thread that is used to sequentially execute all of the scheduled TimerTask
s. If a task takes too long to execute, it can “hog” the single timer thread.
ScheduledThreadPoolExecutor
Java 5 introduced the ScheduledThreadPoolExecutor
class as a versatile replacement for the Timer
and TimerTask
combination. This is a thread pool that can execute tasks that run periodically and/or after a given delay. It accepts delays and repeat periods as TimeUnit
values. This class is in the java.util.concurrent
package.
What’s really nice about using the ScheduledThreadPoolExecutor
is that we can submit Runnable
and Callable
tasks to it. We don’t need to create tasks that extend TimerTask
; tasks only need to implement either the Runnable
or Callable
interface.
When creating a ScheduledThreadPoolExecutor
, we can specify the number of internal service threads in the core thread pool. A ScheduledThreadPoolExecutor
configured with a single thread is the same as using a Timer
object.
We’ve seen how to submit a task to an ExecutorService
implementation. These tasks run to completion and then terminate. If we want to schedule a task that has to run periodically, we can use an implementation of the ScheduledExecutorService
interface. Currently ScheduledThreadPoolExecutor
is the only concrete implementation provided in the JSE API.
Scheduling Tasks
The ScheduledThreadPoolExecutor
is just another implementation of an ExecutorService
with the added functionality of being able to schedule tasks to run regularly and/or after a given delay. It extends both the Executor
and ExecutorService
interfaces.
We have a number of ways to schedule/submit tasks to the ScheduledThreadPoolExecutor
to run. At first it’s a bit confusing when we first read the API documentation. There are five methods to be aware of:
- The
execute()
method. - The overloaded
submit()
methods. - The overloaded
schedule
methods. - The
scheduleAtFixedRate()
andscheduleWithFixedDelay()
methods.
The execute()
method
We use the execute()
method to run a Runnable
task. This method is specified in the Executor
interface. The execute()
method returns void
because it executes a Runnable
task. We’ve seen and used this method before in an earlier post.
The submit()
methods.
We can submit Runnable
and Callable
tasks using the standard submit()
methods defined in the ExecutorService
interface. These method return Future
values. We’ve seen and used these methods before in an earlier post.
The schedule()
methods.
The schedule
methods are defined in the ScheduledExecutorService
interface. The schedule()
methods return a ScheduledFuture
value.
Runnable
and Callable
tasks can be scheduled for one-time execution with or without an initial delay by using the overloaded schedule()
methods. These tasks are often referred to as one-shot actions.
The delay and repeat period parameters passed to the schedule()
methods are calculated relative to when the task is scheduled. These are not absolute dates/times. It is a simple matter to transform an absolute time represented as a Date
to the required form. For example, to schedule at a certain future date, we can use:
Calendar calendar = new GregorianCalendar(year, month, day, hour, minute, second);
Date date = calendar.getTime();
schedule(task, date.getTime()-System.currentTimeMillis(), TimeUnit.MILLISECONDS);
Using the execute()
method is the same as calling schedule()
with a zero delay.
The scheduleAtFixedRate()
and scheduleWithFixedDelay()
methods
The scheduleAtFixedRate()
and scheduleWithFixedDelay()
methods are defined in the ScheduledExecutorService
interface. The methods do exactly what their names imply, and return a ScheduledFuture
value.
We can only schedule Runnable
tasks using these methods. The tasks can be scheduled for repeated execution at regular intervals with or without an initial delay.
The scheduleAtFixedRate()
method executes tasks with a fixed time rate with or without a delay. The following code schedules a task every two seconds with an initial delay of one second:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task running at "
+ System.currentTimeMillis());
int delay = 1;
int rate = 2;
executor.scheduleAtFixedRate(task, delay, rate, TimeUnit.SECONDS);
The scheduleAtFixedRate()
method doesn’t take into account the duration of the task. If the execution of the task takes longer than its repeat interval, then the following executions may start late, but will not execute concurrently.
The scheduleWithFixedDelay()
method works very much like scheduleAtFixedRate()
, but the wait time period applies between the end of a task and the start of the next task.
The following code schedules a task with a duration of two seconds. There is no initial delay, and a fixed delay of one second between the end of an execution and the start of the next execution. This results in the task being executed every three seconds (the delay plus the task execution time). In other words, it will run at 0 seconds, 3 seconds, 6 seconds and so on. The scheduleWithFixedDelay()
method is useful if the duration of the scheduled tasks is unpredictable.
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
try {
TimeUnit.SECONDS.sleep(2);
long time = System.currentTimeMillis();
System.out.println("Task running at " + time);
}
catch (InterruptedException e) {
System.err.println("Task interrupted!");
}
};
int delay = 0;
int rate = 1;
executor.scheduleWithFixedDelay(task, delay, rate, TimeUnit.SECONDS);
If we change the last line to:
executor.scheduleAtFixedRate(task, delay, rate, TimeUnit.SECONDS);
then the task will run every two seconds, which is simply the length of the time of the task execution. A new task will not be created to overlap the running of the original task.
Example
Here’s a Java program very similar to the program in the last post, but using a ScheduledExecutorService
rather than a Timer
. Refer to the JE API documentation for more details.
import java.util.List;
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceTest {
public static void main(String args[]) throws InterruptedException {
// Creating a ScheduledExecutorService
ScheduledExecutorService executor;
executor = Executors.newScheduledThreadPool(5); // pooled
// OR using only a single thread
// executor = Executors.newSingleThreadScheduledExecutor();
// Creating tasks using lambda expressions.
Runnable task1 = () -> System.out.println("Regular task ran at " +
System.currentTimeMillis());
Runnable task2 = () -> System.out.println("Delayed task ran at " +
new Date().toString());
Runnable task3 = () -> System.out.println("Task 3 running at " +
System.currentTimeMillis());
Runnable task4 = () -> System.out.println("Task 4 running at " +
System.currentTimeMillis());
// Scheduling the tasks
// first parameter is obviously the task
// second parameter is the delay in a TimeUnit
// third parameter is the interval in a TimeUnit
int rate = 2;
executor.scheduleAtFixedRate(task1, 1, rate, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(task3, 0, rate*2, TimeUnit.SECONDS);
executor.scheduleAtFixedRate(task4, 0, rate*3, TimeUnit.SECONDS);
// Assign appropriate values to these variables to set a time
// a few seconds in the future for testing fixed time start.
int year, month, day, hour, minute, second;
Calendar calendar = new GregorianCalendar(year, month, day,
hour, minute, second);
Date date = calendar.getTime();
long currentTime = System.currentTimeMillis();
executor.schedule(task2, date.getTime() - currentTime,
TimeUnit.MILLISECONDS);
// Unrelated Thread stuff - for interest
System.out.printf("%n");
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
System.out.printf("Thread group: %s%n", threadGroup);
int activeThreads = threadGroup.activeCount();
System.out.printf("Active thread count: %d%n", activeThreads);
Thread threads[] = new Thread[activeThreads];
threadGroup.enumerate(threads);
for (Thread t : threads) {
System.out.printf("Thread name: %s%n", t.getName());
}
System.out.printf("%n");
// Sleeping the main thread to simulate a running application
TimeUnit.SECONDS.sleep(25);
// Cancel all the tasks
try {
System.out.println("Shutting down the executor service...");
// No new tasks can be submitted,
// but previously submitted tasks will be run.
executor.shutdown();
// Wait for 10 seconds for existing tasks to finish
executor.awaitTermination(10, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
System.err.println("Tasks were interrupted!");
}
finally {
// If all tasks completed following shutdown(), returns true
if (!executor.isTerminated()) {
System.err.println("Cancelling any incomplete tasks...");
}
// Tries to stop all executing tasks,
// and stops processing of waiting tasks
executor.shutdownNow();
System.out.println("Shutdown done!");
}
}
} // end of class
The somewhat unrelated Thread
code in the program is the same as the last post. The interesting part is that as we increase the size of the thread pool e.g., Executors.newScheduledThreadPool(5)
, we will obviously see more threads in the output:
Thread group: java.lang.ThreadGroup[name=main,maxpri=10]
Active thread count: 6
Thread name: main
Thread name: pool-1-thread-1
Thread name: pool-1-thread-2
Thread name: pool-1-thread-3
Thread name: pool-1-thread-4
Thread name: pool-1-thread-5
We can also use Executors.newSingleThreadScheduledExecutor()
to create a ScheduledExecutorService
that uses a single thread to process the tasks.
Ending Off
By this stage, you’re probably all tired of threads, so from the next post, we’ll look at something else interesting Java related. There are always lots and lots of interesting Java topics…!
Please share your comments, tell me about your experiences, and ask your questions!