Threads and Multithreading – Part 10 (The End)

Threads - images of multicoloured threads

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

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 TimerTasks. 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() and scheduleWithFixedDelay() 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!

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.