Threads and Multithreading – Part 8

Threads - images of multicoloured threads

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

Introduction

In the last post we created Executor and ExecutorService objects, and submitted Callable and Runnable objects to them. We also had a brief look at accessing Callable return values using the Future interface.

In this post, we’ll introduce the TimeUnit enum, and look at some of the other methods of the Future interface.

The TimeUnit Enum

We’ve already seen that threads can be put to sleep for a certain length of time by calling the Thread.sleep() method and passing in the required number of milliseconds for the thread to sleep. We could use this to simulate long running tasks, or just to wait until a computation is complete. For example, Thread.sleep(1000) will cause the current thread to sleep for a second (1000 milliseconds).

A small problem with Thread.sleep() is that we sometimes have to do some simple multiplication when setting the sleep time. If we wanted a thread to sleep for an hour, we’d have to multiply 1000 by 60 and by 60 gain to get the number of milliseconds in ah hour. We’d then call Thread.sleep(1000*60*60).

The Concurrency API introduced an enum called TimeUnit so that we can more easily work with units of time. The enum constants are NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS and DAYS.

Each TimeUnit constant has a number of methods including sleep(), timedJoin(), timedWait(), and conversion methods to convert between the different units of time.

Here’s an example which shows how we can use the sleep() method to give us a two second delay:

Runnable task = () -> {
    try {
        String name = Thread.currentThread().getName();
        System.out.println("Before sleeping: " + name);
        TimeUnit.SECONDS.sleep(2);
        System.out.println("After sleeping: " + name);
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Thread thread = new Thread(task);
thread.start();

Probably the three most useful methods of the TimeUnit enum are convenience methods that wrap around other classes’ methods and convert the timeout argument into the required number of milliseconds and nanoseconds:

  • public void sleep(long timeout) — wraps around the Thread.sleep() method.

  • public void timedJoin(Thread thread, long timeout) — wraps around the Thread.join() method.

  • public void timedWait(Object object, long timeout) — wraps around the Object.wait() method.

These methods make our life a little easier. Instead of using Thread.sleep(1000*60*60), we can simply call TimeUnit.HOURS.sleep(1), and it’s also more obvious to the maintainers of our code.

The Future Interface

In last week’s post, we saw that when we submitted a Callable task to an executor service, the service returned a Future object. The submit() methods execute tasks asynchronously and don’t block while we wait for the task to complete. We use the Future object to retrieve the actual result from the Callable task at a later stage when it suits us.

The Future interface contains five methods:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    V get();
    V get(long timeout, TimeUnit unit);
    boolean isCancelled();
    boolean isDone();
}

We use the isDone() method to check if the task is completed. This gives us the opportunity to either do something else in our program while the task executes, or decide to wait at that point for the task to complete.

The two get() methods get the value returned by the task. One waits (blocks) indefinitely for the task to complete; the other waits at most till the timeout expires, and then tries to retrieve the result. As can be imagined, the methods can throw a multitude of exceptions if things go wrong. Check the JSE API documentation for more details.

We can try to cancel the task with the cancel() method. We can check to see whether the task was cancelled before it finished normally with the isCancelled() method.

Remember that we can think of a Future as a placeholder that stands in for a future result. We can submit a task to an executor service, and largely forget about it until we need the result of the task. At that point, we can query the Future with future.isDone() to find out whether the task is done. If it’s done, we can get the result by calling future.get(). If not, we can either get busy with something else, or we can block at that point to wait for the task to complete.

Here’s a snippet of code from last week to remind you:

Callable<Integer> task = () -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        return 42;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("Task interrupted!", e);
    }
};

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);

System.out.println("Future done? " + future.isDone());

Integer result = future.get(); // blocks here until the Future is done

System.out.println("Future done? " + future.isDone());
System.out.println("Result of future = " + result);

Here we submitted the Callable task to an ExecutorService. If we immediately check whether the Future has finished, isDone() will return false because the task has a delay in it. Calling the get() method blocks the current thread and waits until the task has finished before returning the result. At this stage, calling isDone() now returns true.

Timeouts

A call to future.get() will block until the Callable task has finished or been terminated. In the worst case scenario, a Callable can run forever. This would make our application unresponsive. This can be fixed by passing a timeout to the future.get() method:

ExecutorService executor = Executors.newFixedThreadPool(1);

Callable<Integer> task = () -> {
    try {
        TimeUnit.SECONDS.sleep(3); // sleep for 3 seconds
        return 42;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("Task interrupted!", e);
    }
};

Future<Integer> future = executor.submit(task);

future.get(1, TimeUnit.SECONDS); // time out after 1 second

Executing this code will result in a TimeoutException:

Exception in thread "main" java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask.get(FutureTask.java:205)
        at ConcurrencyTest5.main(ConcurrencyTest5.java:34)

The exception was thrown because a one second maximum wait time was passed to future.get(), but the Callable delayed for three seconds before returning the result.

Ending Off

In the next post we’ll look at executing scheduled tasks using the original Timer and TimerTask classes from Java 1.3, as well as with the more modern ScheduledExecutorService.

Another thing we’ll look at next week is shutting down the executor service correctly. If we run the code in this and last week’s post, we’ll find that the program hangs and doesn’t terminate. We have to press Ctrl-C on Windows/Linux to terminate the JVM.

Are you on your way to becoming an expert on Java threads? Has this series helped you? Please share your comments and questions.

Until then, 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.