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:
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.
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.
newSingleThreadExecutor()
Method:- Creates a single-threaded executor that uses a single worker thread.
- Useful when tasks need to be processed sequentially.
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.
newWorkStealingPool()
Method:- Creates a work-stealing thread pool using all available processors.
- Suitable for parallel tasks that can be divided into subtasks.
Prefer using the ExecutorService
interface over the Executor
interface for better control and management of thread execution in concurrent applications.
Explain the significance of the Callable
interface in conjunction with the ExecutorService
. How does it differ from using the Runnable
interface?