Skip to main content

Executors vs ExecutorService

Introduction to Executors:

The Executors class is a utility class that simplifies the creation and management of thread pools. A thread pool is a collection of threads that can be efficiently reused for executing tasks, thereby avoiding the overhead of thread creation for each task.

Creating Executors:

You can create different types of thread pools using various factory methods provided by the Executors class. One commonly used method is newFixedThreadPool, which creates a thread pool with a fixed number of threads.

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorsExample {
public static void main(String[] args) {
// Creating a fixed-size thread pool with three threads
ExecutorService executor = Executors.newFixedThreadPool(3);

// Submitting tasks to the executor
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by thread: " + Thread.currentThread().getName());
});
}

// Shutting down the executor
executor.shutdown();
}
}

Output:

Task 0 executed by thread: pool-1-thread-1
Task 1 executed by thread: pool-1-thread-2
Task 2 executed by thread: pool-1-thread-3
Task 3 executed by thread: pool-1-thread-1
Task 4 executed by thread: pool-1-thread-2

ExecutorService in Java:

The ExecutorService interface extends the Executor interface, providing additional features for controlling and managing the execution of threads and tasks. It offers a more complete framework for concurrent programming.

Creating ExecutorService:

You can create an ExecutorService using the Executors.newFixedThreadPool method or other methods based on your requirements. This interface is capable of managing the lifecycle of threads and tasks more explicitly.

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceExample {
public static void main(String[] args) {
// Creating an ExecutorService with three threads
ExecutorService executorService = Executors.newFixedThreadPool(3);

// Submitting tasks to the ExecutorService
for (int i = 0; i < 5; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " executed by thread: " + Thread.currentThread().getName());
});
}

// Shutting down the ExecutorService
executorService.shutdown();
}
}

Output:

Task 0 executed by thread: pool-1-thread-1
Task 1 executed by thread: pool-1-thread-2
Task 2 executed by thread: pool-1-thread-3
Task 3 executed by thread: pool-1-thread-1
Task 4 executed by thread: pool-1-thread-2

ExecutorService Lifecycle:

The ExecutorService interface provides methods like shutdown and shutdownNow to control the lifecycle of the executor. shutdown allows previously submitted tasks to execute before terminating, while shutdownNow attempts to stop all actively executing tasks.

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceLifecycleExample {
public static void main(String[] args) {
// Creating an ExecutorService with three threads
ExecutorService executorService = Executors.newFixedThreadPool(3);

// Submitting tasks to the ExecutorService
for (int i = 0; i < 5; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Task " + taskId + " executed by thread: " + Thread.currentThread().getName());
});
}

// Initiating shutdown of the ExecutorService
executorService.shutdown();

// Further tasks submitted after shutdown will throw RejectedExecutionException
// executorService.submit(() -> System.out.println("This task will not be executed."));
}
}

Output:

Task 0 executed by thread: pool-1-thread-1
Task 1 executed by thread: pool-1-thread-2
Task 2 executed by thread: pool-1-thread-3
Task 3 executed by thread: pool-1-thread-1
Task 4 executed by thread: pool-1-thread-2

Key Differences:

  • Executors is a utility class: It simplifies the creation of different types of executor services.

  • ExecutorService is an interface: It extends the Executor interface, providing additional features for managing thread execution.

  • ExecutorService is more feature-rich: It offers a more

    comprehensive framework for managing the lifecycle of threads and tasks.

Commonly Used Factory Methods and Use Cases:

  1. newFixedThreadPool(int nThreads) Method:

    • Creates a thread pool with a fixed number of threads.
    • Useful when you want to limit the number of concurrent threads.
  2. newCachedThreadPool() Method:

    • Creates a thread pool that creates new threads as needed and reuses existing ones.
    • Suitable for tasks that are short-lived and frequent.
  3. newSingleThreadExecutor() Method:

    • Creates a single-threaded executor that uses a single worker thread.
    • Useful when tasks need to be processed sequentially.
  4. newScheduledThreadPool(int corePoolSize) Method:

    • Creates a thread pool that can schedule tasks to run after a delay or periodically.
    • Ideal for scenarios requiring scheduled execution of tasks.
  5. newWorkStealingPool() Method:

    • Creates a work-stealing thread pool using all available processors.
    • Suitable for parallel tasks that can be divided into subtasks.
tip

Prefer using the ExecutorService interface over the Executor interface for better control and management of thread execution in concurrent applications.

Challenge Question

Explain the significance of the Callable interface in conjunction with the ExecutorService. How does it differ from using the Runnable interface?