This is part 9 of this series. If you missed any of the last eight, 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
Introduction
In the last post we introduced the TimeUnit
enum, and looked at the Future
interface in more detail.
In this post, we’ll learn how to gracefully shut down Executor
and ExecutorService
objects. We’ll also learn how to schedule tasks to run regularly with the original Timer
and TimerTask
classes.
Shutting Down Executors
When running the code in the previous few posts, we probably would have noticed that the Java JVM didn’t stop automatically. It appeared to hang. Executor services have to be stopped explicitly, otherwise they just continually wait for new tasks to be submitted to them. We had to press Ctrl-C on Windows/Linux to terminate the JVM.
An ExecutorService
provides two shutdown methods:
-
shutdown()
waits for currently running tasks to finish. -
shutdownNow()
interrupts all running tasks and shuts the service down immediately.
In the following example, the service tries to shut down gracefully by waiting for any currently running tasks to end. After five seconds the service forces a shutdown by interrupting all tasks that are still running:
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 have completed following the shutdown() call, will return 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();
// If we want to know which tasks are still waiting to run,
// we can store and iterate over the returned List.
// List<Runnable> tasksStillWaitingToRun = executor.shutdownNow();
System.out.println("Shutdown done!");
}
There is another example of shutting down an executor service in the JSE API documentation in the ExecutorService
interface page.
Future
s are tightly coupled to the underlying executor service. Every non-terminated Future
will throw exceptions if the service is shut down:
executor.shutdownNow();
future.get(); // will throw an exception
Timers and TimerTasks
In this subsection, we’ll look at scheduling threads for future execution, either as once-off tasks, or as repeated tasks at regular intervals.
Java 1.3 provided two simple classes to schedule tasks for future execution. These tasks are registered with a background thread, which then executes them, either as one-time tasks, or as repeated tasks at regular intervals.
These two classes are the Timer
and TimerTask
classes in the java.util
package. A TimerTask
represents the scheduled task. The Timer
class schedules and executes those tasks. The Timer
class is thread-safe, which allows multiple threads to share a single Timer
object without needing to write synchronized methods.
We can create a Timer
as a daemon thread if needed. We would do this if the Timer
object is going to be used to schedule repeating maintenance-type tasks. These tasks would need to run regularly while the application is running, but the tasks do not need to run once the application has ended.
To create tasks that can be scheduled with a Timer
, we must extend the TimerTask
class and implement the run()
method to do whatever has to be regularly scheduled.
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.
The following is a small Java program that creates a Timer
and two TimerTask
implementations to be run regularly. You can choose to use the schedule()
or scheduleAtFixedRate()
methods. There will be very little difference in the output because the tasks do almost nothing. If there are delays due to background processing such as garbage collection, subsequent executions will be delayed when using the fixed-delay schedule()
method. Refer to the JE API documentation for more details.
import java.util.Timer;
import java.util.TimerTask;
public class TimerTaskTest {
public static void main(String args[]) throws InterruptedException {
final String fmt = "Task %d running every %4d milliseconds%n";
TimerTask task1 = new TimerTask() {
private long lastTime = System.currentTimeMillis();
@Override
public void run() {
System.out.printf(fmt, 1, System.currentTimeMillis() - lastTime);
lastTime = scheduledExecutionTime();
}
};
TimerTask task2 = new TimerTask() {
private long lastTime = System.currentTimeMillis();
@Override
public void run() {
System.out.printf(fmt, 2, System.currentTimeMillis() - lastTime);
lastTime = scheduledExecutionTime();
}
};
// Creating the Timer. We can optionally pass a name and/or
// a boolean flag for a daemon Timer.
Timer timer = new Timer("Test timer");
// Unrelated Thread stuff - for interest
System.out.printf("%n");
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
System.out.printf("Thread group: %s%n", threadGroup);
int activeThreads = Thread.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");
// Scheduling the tasks
// first parameter is obviously the task
// second parameter is the delay in milliseconds
// third parameter is the interval period in milliseconds
// Schedules the tasks for repeated fixed *delay* execution - each execution is
// scheduled relative to the *actual* execution time of the *previous* execution.
timer.schedule(task1, 2000, 1000);
timer.schedule(task2, 3000, 1500);
// OR
// Schedules the tasks for repeated fixed *rate* execution - each execution is
// scheduled relative to the *scheduled* execution time of the *initial* execution.
// timer.scheduleAtFixedRate(task1, 2000, 1000);
// timer.scheduleAtFixedRate(task2, 3000, 1500);
// Sleeping the main thread to simulate a running application
Thread.sleep(15000);
// Cancel all the tasks
timer.cancel();
System.out.printf("%nTimer cancelled...%n");
}
} // end of class
There’s some unrelated Thread
code in the program just for interest. We get a reference to the current ThreadGroup
, from which we can find out how many threads are running and get their names. The output will be similar to the following:
Thread group: java.lang.ThreadGroup[name=main,maxpri=10]
Active thread count: 2
Thread name: main
Thread name: Test timer
From this, we can see that the Timer
only creates a single thread to process the TimerTask
s.
Ending Off
There are a few problems when using Timer
s and TimerTask
s, which we’ll look at in the next post. We’ll also look at scheduling tasks using the more versatile ScheduledThreadPoolExecutor
class.
Please share your comments. We’ve almost finished with threads, so I’d love to hear what topic you’d like me to cover.
Until next time, stay safe and keep learning!