Java > Concurrency and Multithreading > Executors and Thread Pools > Thread Pools and Task Management
Basic Thread Pool Example
This snippet demonstrates a basic thread pool using `ExecutorService` to manage a fixed number of threads for executing tasks.
Code Snippet
This code initializes a fixed-size thread pool with 5 threads using `Executors.newFixedThreadPool(5)`. It then submits 10 tasks to this pool. Each task simulates work by sleeping for 1 second and prints a message indicating which thread executed it. Finally, the `shutdown()` method is called to prevent the executor from accepting new tasks and the program waits until all submitted tasks are completed, which is verified by `executor.isTerminated()`.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// Create a fixed-size thread pool with 5 threads
ExecutorService executor = Executors.newFixedThreadPool(5);
// Submit 10 tasks to the executor
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // Simulate some work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Shut down the executor after all tasks are submitted
executor.shutdown();
// Wait for all tasks to complete (optional)
while (!executor.isTerminated()) {
// You can add a small sleep here to avoid busy-waiting
// try { Thread.sleep(100); } catch (InterruptedException e) {}
}
System.out.println("All tasks completed.");
}
}
Concepts Behind the Snippet
The key concept is using an `ExecutorService` to manage a pool of threads. Instead of creating and managing threads manually, the `ExecutorService` handles the thread lifecycle, task assignment, and thread reuse. This improves performance and simplifies concurrency management. `Executors.newFixedThreadPool(int)` creates a thread pool with a fixed number of threads. Tasks are submitted using `executor.submit(Runnable)`, and the executor automatically assigns them to available threads. The `executor.shutdown()` method signals that no new tasks will be accepted, and the pool will shut down once all submitted tasks have completed.
Real-Life Use Case
Thread pools are heavily used in web servers to handle incoming requests. Each request can be treated as a task and submitted to a thread pool. This allows the server to handle multiple requests concurrently without creating a new thread for each request, which is resource-intensive. Other use cases include processing large datasets in parallel, handling asynchronous operations, and performing background tasks.
Best Practices
Interview Tip
Be prepared to discuss the advantages of using thread pools over creating individual threads for each task. Understand the different types of thread pools and when to use each one. Also, be ready to explain how thread pools improve performance, reduce resource consumption, and simplify concurrency management.
When to Use Them
Use thread pools when you have a large number of short-lived tasks that need to be executed concurrently. Thread pools are particularly useful in situations where creating a new thread for each task would be too expensive in terms of resource consumption.
Memory Footprint
Thread pools help manage memory by limiting the number of active threads. A fixed-size thread pool will only allocate memory for the specified number of threads, preventing uncontrolled thread creation and potential memory exhaustion. However, the queue used to hold pending tasks can still consume significant memory if tasks are submitted faster than they can be processed. Monitor the queue size to prevent memory issues.
Alternatives
Pros
Cons
FAQ
-
What happens if I submit more tasks than the thread pool size?
The extra tasks are placed in a queue, waiting for a thread to become available. The specific queuing behavior depends on the type of `ExecutorService` used. The default implementation uses an unbounded queue, which can potentially lead to memory issues if tasks are submitted faster than they can be processed. -
How do I know when all tasks have completed?
You can use the `executor.isTerminated()` method to check if the executor has finished shutting down and all tasks have completed. This should be checked after calling `executor.shutdown()`. -
What is the difference between `shutdown()` and `shutdownNow()`?
`shutdown()` prevents the executor from accepting new tasks and allows already submitted tasks to complete. `shutdownNow()` attempts to stop all actively executing tasks and halt the processing of waiting tasks. `shutdownNow()` returns a list of tasks that were awaiting execution.