Java SE 8/11 Programmer II Exam Series: Concurrency (Part 1)

Introduction

This post is currently in progress…

In this post, we will look at the exam objective; create worker threads using Runnable, Callable, and use an ExecutorService to concurrently execute tasks. Note that this is also a part of the Java SE 11 Programmer II Certification.

Runnable Interface

Runnable interface

@FunctionalInterface public interface Runnable {
  void run();
}

Usages of Runnable interface

() -> System.out.println("Hello World")
() -> {int i=10; i++;}
() -> {return;}
() -> {}

Creating a Thread

There are two steps to create a Thread: (1) Define the Thread with the corresponding task. You can either implement the Runnable interface or extend the Thread class. (2) Use the Thread.start() method to start the task. note that the order is not guaranteed.

Here are the examples from the API specification. Define a class that implements the Runnable interface:

   class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

Create a thread and start the task:

public static void main(String[] args) {
  PrimeRun p = new PrimeRun(143);
  new Thread(p).start();
}

Define a class that extends the Thread class

     class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }

     }

Create a thread and start the task:

public static void main(String[] args) {
  PrimeThread p = new PrimeThread(143);
  p.start();
}

Extending Thread class vs Implementing Runnable

There are different use cases where you prefer implementing Runnable over extending a class. These are the some cases covered in the OCP book,

  • If you need to define your own Thread rules upon which multiple tasks will rely, such as a priority Thread, extending Thread may be preferable.
  • Since Java doesn’t support multiple inheritance, extending Thread does not allow you to extend any other class, whereas implementing Runnable lets you extend another class.
  • Implementing Runnable is often a better object-oriented design practice since it separates the task being performed from the Thread object performing it.
  • Implementing Runnable allows the class to be used by numerous Concurrency API classes.

Concurrently execute tasks with ExecutorService

It is recommended that you use the ExecutorService framework when you need to create and execute a separate task. You can use ExecutorService for both a single thread or multiple threads.

//TODO - Example here

Single-Thread Executor

In this example, we used the newSingleThreadExecutor() method, which is the simplest ExecutorService that we could create. With a single-thread executor, results are guaranteed to be executed in the order in which they are added to the executor service

//TODO - Example here

Shutting Down a Thread Executor

Once you have finished using a thread executor, it is important that you call the shutdown() method. A thread executor creates a non-daemon thread on the first task that is executed, so failing to call shutdown() will result in your application never terminating

//TODO - Example here

Unfortunately, the ExecutorService interface does not implement AutoCloseable, so you cannot use a try-with-resources statement. Using Try finally works for thread executors that are used once and thrown away, it does not work for thread executors that are persistent throughout the life of the application. In such a scenario, you would need to define a static method that can be called anytime the user signals that they wish to exit the program

Submitting Tasks

You can submit tasks to an ExecutorService instance in multiple ways. There are 5 different methods, which are described below, to submit a task.

MethodDescription
execute(Runnable command)Executes the given command at some time in the future. The method is inherited from java.util.concurrent.Executor
submit(Runnable task)Submits a Runnable task for execution and returns a Future representing that task
submit(Callable task)Submits a value-returning task for execution and returns a Future representing the pending results of the task
invokeAll(Collection> tasks)Executes the given tasks, returning a list of Futures holding their status and results when all complete
invokeAny(Collection> tasks)Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do

Waiting for Results

How do we know when a task submitted to an ExecutorService is complete?The Future class includes methods that are useful in determining the state of a task, as shown in the table below.

Modifier and TypeMethodDescription
booleancancel​(boolean mayInterruptIfRunning)Attempts to cancel execution of this task.
Vget()Waits if necessary for the computation to complete, and then retrieves its result.
Vget​(long timeout, TimeUnit unit)Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if available.
booleanisCancelled()Returns true if this task was cancelled before it completed normally.
booleanisDone()Returns true if this task completed.

Here is an example from API Documentation:

 interface ArchiveSearcher { String search(String target); }
 class App {
   ExecutorService executor = ...
   ArchiveSearcher searcher = ...
   void showSearch(String target) throws InterruptedException {
     Callable<String> task = () -> searcher.search(target);
     Future<String> future = executor.submit(task);
     displayOtherThings(); // do other things while searching
     try {
       displayText(future.get()); // use future
     } catch (ExecutionException ex) { cleanup(); return; }
   }
 }

The above construction with submit could be replaced by:

FutureTask<String> future = new FutureTask<>(task);
executor.execute(future);

Callable Interface

The Callable interface was introduced as an alternative to the Runnable interface, since it allows more details to be retrieved easily from the task after it is completed.

@FunctionalInterface public interface Callable<V> {
   V call() throws Exception;
}

The difference with Callable is that the get() methods on a Future object return the matching generic type or null.

awaitTermination()

After submitting a set of tasks to a thread executor, it is common to wait for the results.

//TODO - Example here

Scheduling Tasks

We often need to schedule a task to happen at some future time. We might even need to schedule the task to happen repeatedly, at some set interval. There are five factory methods in ScheduledExecutorService. Each of the ScheduledExecutorService methods is important and has real-world applications.

Modifier and TypeMethodDescriptionReal-life Usage
ScheduledFuture<?>schedule​(Runnable command, long delay, TimeUnit unit)Submits a one-shot task that becomes enabled after the given delay.Useful to check on the state of processing a task and send out notifications if it is not finished
<V> ScheduledFuture<V>schedule​(Callable<V> callable, long delay, TimeUnit unit)Submits a value-returning one-shot task that becomes enabled after the given delay.Useful to check on the state of processing a task and
send out notifications if it is not finished
ScheduledFuture<?>scheduleAtFixedRate​(Runnable command, long initialDelay, long period, TimeUnit unit)Submits a periodic action that becomes enabled first after the given initial delay, and subsequently with
the given period; that is, executions will commence after initialDelay, then initialDelay + period, then initialDelay + 2 * period, and so on.
Useful for tasks that need to be run at specific intervals
ScheduledFuture<?>scheduleWithFixedDelay​(Runnable command, long initialDelay, long delay, TimeUnit unit)Submits a periodic action that becomes enabled first after the given initial delay, and subsequently with the given delay between the termination of one execution and the commencement of the next.Useful for processes that you want to happen
repeatedly but whose specific time is unimportant

Here is an example from API specification. We first create an instance of ScheduledExecutorService and call a schedular.

 import static java.util.concurrent.TimeUnit.*;
 class BeeperControl {
   private final ScheduledExecutorService scheduler =
     Executors.newScheduledThreadPool(1);

   public void beepForAnHour() {
     final Runnable beeper = new Runnable() {
       public void run() { System.out.println("beep"); }
     };
     final ScheduledFuture<?> beeperHandle =
       scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
     scheduler.schedule(new Runnable() {
       public void run() { beeperHandle.cancel(true); }
     }, 60 * 60, SECONDS);
   }
 }

Increasing Concurrency with Pools

A thread pool is a group of pre-instantiated reusable threads that are available to perform a set of arbitrary tasks

The difference between a single-thread and a pooled-thread executor is what happens when a task is already running. While a single-thread executor will wait for an available thread to become available before running the next task, a pooled-thread executor can execute the next task concurrently. If the pool runs out of available threads, the task will be queued by the thread executor and wait to be completed.

newCachedThreadPool()

This is commonly used for pools that require executing many short-lived asynchronous tasks. For long-lived processes, usage of this executor is strongly discouraged

//TODO - Example here

newFixedThreadPool()

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue

//TODO - Example here

newScheduledThreadPool()

This is identical to the newFixedThreadPool() method, except that it returns an instance of ScheduledExecutorService. This executor has subtle differences in the way that the scheduleAtFixedRate() performs. For example:

ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.scheduleAtFixedRate(command,3,1,TimeUnit.MINUTE);

Leave a Reply

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