Threads and Multithreading – Part 9

Threads - images of multicoloured threads

This is part 9 of this series. If you missed any of the last eight, you can find them here:

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.

Futures 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 TimerTasks.

Ending Off

There are a few problems when using Timers and TimerTasks, 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!

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.