Skip to main content

Executors vs ExecutorService

  • Java's java.util.concurrent package provides powerful utilities for managing threads and concurrency.

  • Two essential components of this package are Executors and ExecutorService.

  • They simplify the creation, management, and control of thread pools, making it easier to write concurrent programs.

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.

Key Methods:

  • submit(): Submits a task for execution and returns a Future representing the task's result.

  • invokeAll(): Executes a collection of tasks and returns a list of Future objects.

  • shutdown(): Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.

  • shutdownNow(): Attempts to stop all actively executing tasks and halts the processing of waiting tasks.

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

Using ExecutorService for Different Scenarios

Fixed Thread Pool:

A fixed thread pool contains a fixed number of threads. If all threads are busy, new tasks will wait in a queue until a thread becomes available.

Example:

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

public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);

for (int i = 0; i < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on " + Thread.currentThread().getName());
});
}

executorService.shutdown();
}
}

Cached Thread Pool:

A cached thread pool creates new threads as needed and reuses previously constructed threads when they are available. This is suitable for short-lived asynchronous tasks.

Example:

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

public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on " + Thread.currentThread().getName());
});
}

executorService.shutdown();
}
}

Single Thread Executor:

A single thread executor uses a single worker thread to execute tasks. Tasks are executed sequentially.

Example:

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

public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();

for (int i = 0; i < 5; i++) {
int taskNumber = i;
executorService.submit(() -> {
System.out.println("Task " + taskNumber + " is running on " + Thread.currentThread().getName());
});
}

executorService.shutdown();
}
}

Scheduled Thread Pool:

A scheduled thread pool allows you to schedule tasks to run after a delay or to execute periodically.

Example:

import java.util.concurrent.*;

public class Main {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

Runnable task = () -> System.out.println("Task is running at " + System.currentTimeMillis());

scheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS);

scheduledExecutorService.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);

try {
Thread.sleep(10000); // Let the tasks run for a while
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
scheduledExecutorService.shutdown();
}
}
}

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.

The lifecycle of an ExecutorService is managed through its state transitions and key methods.

  • Creation: The ExecutorService is instantiated using factory methods from the Executors class.

  • Running: Tasks are submitted for execution.

  • Shutting Down: The shutdown() or shutdownNow() methods are called to stop accepting new tasks.

  • Termination: All running tasks complete, and the ExecutorService is fully shut down.

Lifecycle Methods:

  • submit(): Submits a task for execution.

  • shutdown(): Initiates an orderly shutdown, where previously submitted tasks are executed, but no new tasks will be accepted.

  • shutdownNow(): Attempts to stop all actively executing tasks and halts the processing of waiting tasks.

  • isShutdown(): Returns true if the ExecutorService has been shut down.

  • isTerminated(): Returns true if all tasks have completed following shut down.

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:

FeatureExecutorsExecutorService
TypeUtility classInterface
PurposeProvides factory methods for creatingRepresents a thread pool for executing tasks
MethodsFactory methods like newFixedThreadPool()Task submission methods like submit(), invokeAll()
Lifecycle ControlNot applicableMethods to manage lifecycle: shutdown(), shutdownNow()
UsageSimplifies creation of thread poolsManages task execution and lifecycle of thread pools

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.

  1. 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.

  1. newSingleThreadExecutor() Method:

    • Creates a single-threaded executor that uses a single worker thread.

    • Useful when tasks need to be processed sequentially.

  1. 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.

  1. newWorkStealingPool() Method:

    • Creates a work-stealing thread pool using all available processors.

    • Suitable for parallel tasks that can be divided into subtasks.

Summary:

  • Executors: A utility class to create various types of thread pools.

  • ExecutorService: An interface that manages the execution of asynchronous tasks and provides control over thread lifecycle.

  • Thread Pools: Improve resource management and performance by reusing threads for multiple tasks.

  • ScheduledExecutorService: Allows for scheduling tasks with a delay or at fixed intervals.

  • Lifecycle Management: Proper shutdown and lifecycle management are crucial for resource management.

  • Key Differences: Executors provide factory methods, while ExecutorService manages task execution and lifecycle.

Understanding Executors and ExecutorService is essential for writing efficient and scalable concurrent programs in Java.