Executor framework

Executor framework

Started since java 1.5

Callable and Runnable

Runnable (vs) Callable comes into point when we are using Executer framework.

ExecutorService is a subinterface of Executor interface, which accepts both Runnable and Callable tasks.

https://docs.oracle.com/javase/tutorial/essential/concurrency/exinter.html

Before java 1.5, multi-threading was implemented using Interface Runnable. Runnable was part of java since 1.0

The problem with Runnables is, after the thread task is completed, we do not have a way to collect the Threads information (the results from the execution of those threads).

Callables were introduced to solve this problem.

There are a few different ways to delegate tasks for execution to an ExecutorService.

  1. execute(Runnable task):void crates new thread but not blocks main thread or caller thread as this method return void.
  2. submit(Callable<?>):Future<?>, submit(Runnable):Future<?> crates new thread and blocks main thread when you are using future.get()

java.util.concurrent package

The java.util.concurrent package defines three executor interfaces:

  1. Executor, a simple interface that supports launching new tasks.
  2. ExecutorService, a subinterface of Executor, which adds features that help manage the life cycle, both of the individual tasks and of the executor itself.
  3. ScheduledExecutorService, a subinterface of ExecutorService, supports future and/or periodic execution of tasks.

The Executor Interface

The Executor interface provides a single method, execute, designed to be a drop-in replacement for a common thread-creation idiom. If r is a Runnable object, and e is an Executor object you can replace

(new Thread(r)).start();

with

e.execute(r);

Drawbacks

The Executor implementation, execute may do the same thing, but is more likely to use an existing worker thread to run r, or to place r in a queue to wait for a worker thread to become available.

The ExecutorService

ExecutorService can execute Runnable and Callable tasks.

How to instantiate?

https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/Executors.html

https://github.com/explorer436/programming-playground/tree/main/java-playground/java-multithreading-and-concurrency-demo

Assigning tasks to ExecutorService

We can assign tasks to the ExecutorService using several methods including execute(), which is inherited from the Executor interface, and also submit(), invokeAny() and invokeAll().

The execute() method is void and doesn’t give any possibility to get the result of a task’s execution or to check the task’s status (is it running)

executorService.execute(runnableTask);

submit() submits a Callable or a Runnable task to an ExecutorService and returns a result of type Future:

See LaunchingCallableUsingExecutorService.java

Future<String> future = executorService.submit(callableTask);

invokeAny() assigns a collection of tasks to an ExecutorService, causing each to run, and returns the result of a successful execution of one task (if there was a successful execution):

String result = executorService.invokeAny(callableTasks);

invokeAll() assigns a collection of tasks to an ExecutorService, causing each to run, and returns the result of all task executions in the form of a list of objects of type Future:

List<Future<String>> futures = executorService.invokeAll(callableTasks);

Common pitfalls

Despite the relative simplicity of ExecutorService, there are a few common pitfalls.

  1. Keeping an unused ExecutorService alive: See the detailed explanation in Section 4 on how to shut down an ExecutorService.
  2. Wrong thread-pool capacity while using fixed length thread pool: It is very important to determine how many threads the application will need to run tasks efficiently. A too-large thread pool will cause unnecessary overhead just to create threads that will mostly be in the waiting mode. Too few can make an application seem unresponsive because of long waiting periods for tasks in the queue.
  3. Calling a Future‘s get() method after task cancellation: Attempting to get the result of an already canceled task triggers a CancellationException.
  4. Unexpectedly long blocking with Future‘s get() method: We should use timeouts to avoid unexpected waits.

References

  1. https://docs.oracle.com/javase/tutorial/essential/concurrency/exinter.html
  2. https://www.baeldung.com/java-executor-service-tutorial

Tags

  1. Callable and Runnable

Links to this note