Java > Concurrency and Multithreading > Executors and Thread Pools > ExecutorService
Basic ExecutorService Example: Submitting Tasks
ExecutorService
provides a powerful way to manage threads and execute tasks asynchronously. This example demonstrates how to submit tasks to an ExecutorService
and retrieve results using Future
objects. This is a fundamental example for understanding thread pool management.
Code Snippet
This code creates a fixed-size thread pool using Executors.newFixedThreadPool(2)
. A Callable
represents a task that returns a value. The executor.submit(task)
method submits the task to the thread pool and returns a Future
object. The Future.get()
method blocks until the task completes and returns the result. Finally, executor.shutdown()
initiates an orderly shutdown of the executor, allowing previously submitted tasks to complete.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
public class ExecutorServiceExample {
public static void main(String[] args) throws Exception {
// Create a fixed-size thread pool with 2 threads
ExecutorService executor = Executors.newFixedThreadPool(2);
// Define a task that returns a value
Callable<String> task = () -> {
System.out.println("Task started by thread: " + Thread.currentThread().getName());
Thread.sleep(2000); // Simulate some work
return "Task completed";
};
// Submit the task to the executor and get a Future
Future<String> future = executor.submit(task);
// Perform other operations while the task is running
System.out.println("Main thread doing other work: " + Thread.currentThread().getName());
// Get the result of the task (this will block until the task is complete)
String result = future.get();
System.out.println("Result: " + result);
// Shutdown the executor
executor.shutdown();
}
}
Concepts Behind the Snippet
ExecutorService
decouples task submission from task execution. It manages a pool of threads, reusing them to execute multiple tasks, which reduces the overhead of creating new threads for each task. Callable
is an interface similar to Runnable
, but it can return a value and throw checked exceptions. Future
represents the result of an asynchronous computation. It provides methods to check if the computation is complete, wait for its completion, and retrieve the result.
Real-Life Use Case
In a web server, handling multiple client requests concurrently. Each request can be submitted as a task to an ExecutorService
, allowing the server to process multiple requests in parallel without blocking the main thread. This improves responsiveness and throughput.
Best Practices
ExecutorService
after use to release resources. Use shutdown()
for a graceful shutdown or shutdownNow()
for an immediate shutdown.ExecutorService
.
Interview Tip
Be prepared to discuss the different types of thread pools available through the Executors
factory class (e.g., fixed-size, cached, scheduled). Also, explain the difference between shutdown()
and shutdownNow()
.
When to Use Them
Use ExecutorService
when you need to execute tasks asynchronously and manage a pool of threads efficiently. It's particularly useful for CPU-bound tasks or I/O-bound tasks that can be executed concurrently.
Memory Footprint
The memory footprint depends on the size of the thread pool and the tasks being executed. A larger thread pool consumes more memory. Careful consideration should be given to sizing the thread pool based on available resources.
Alternatives
Alternatives to ExecutorService
include using raw threads (Thread
class), but this is generally discouraged due to the complexity of managing threads manually. Another alternative for simpler concurrency scenarios is using the CompletableFuture
API.
Pros
Cons
FAQ
-
What is the difference between
Runnable
andCallable
?
Runnable
is a functional interface that represents a task that does not return a value.Callable
is similar, but it can return a value and throw checked exceptions. -
What happens if I don't call
executor.shutdown()
?
The application may not terminate because the non-daemon threads in the thread pool will continue to run, waiting for new tasks. This can lead to resource leaks and prevent the JVM from exiting. -
How do I handle exceptions thrown by tasks submitted to the
ExecutorService
?
Wrap the task's code in atry-catch
block to catch and handle exceptions. You can log the exception, retry the task, or perform other error handling actions. Remember to deal with exceptions inside theCallable
orRunnable
'scall()
orrun()
method respectively.