Module 15: Advanced Concurrency and Async Design
Executor Services, Thread Pools, and Task Scheduling
Use executor services to manage concurrency deliberately instead of creating threads everywhere. This lesson covers pool choices, shutdown strategy, backpressure concerns, and where modern virtual-thread executors can simplify blocking work.
Author
Java Learner Editorial Team
Reviewer
Technical review by Java Learner
Last reviewed
2026-04-17
Java version
Java 25 LTS
Learning goals
- Pick a sensible executor style for fixed, scheduled, or request-style workloads
- Shut down executors cleanly and understand what happens to queued tasks
- Recognize when a virtual-thread-per-task executor is a better fit for blocking I/O style work
Before you start
- You are comfortable with classes, methods, exceptions, and collections
Lesson roadmap: Start with the mental model, then follow the design choices, common pitfalls, and the practical workflow you should apply in a real project.
Executor services separate task submission from thread management: That keeps the code focused on work rather than thread creation details.
Thread pools prevent thread explosion: Instead of creating unlimited threads, a pool reuses a controlled set of workers.
Shutdown matters: A pool that is never shut down can keep the process alive or leak resources.
Practical rule: Fixed pools are good defaults for bounded workloads. Scheduled executors fit repeated or delayed work.
Pool selection is a design choice: A fixed pool limits parallelism, a scheduled executor handles delayed or repeated jobs, and a single-thread executor can serialize work when order matters more than throughput.
Queueing changes behavior: Submitting tasks faster than workers can finish them does not make the system faster. It often just moves the pressure into the queue, memory usage, and response times.
Modern note: Java now supports virtual-thread executors for workloads that spend a lot of time blocked on I/O. They simplify code that would otherwise need large platform-thread pools, but they are not magic speed buttons for CPU-heavy work.
Operational checklist: Decide who owns executor creation, who shuts it down, how long the app waits during shutdown, and what happens to tasks that never finish.
How to study this module: Run each example more than once, print the current thread name, and change the workload size. Concurrency concepts only become real when you can see scheduling, waiting, and shared-state behavior.
Code review mindset: In concurrent code, correctness comes before throughput. First ask who owns the data, who can mutate it, and what guarantees make each read or write safe.
Production habit: Prefer small, explicit concurrency boundaries. Immutable data, bounded executors, cancellation support, and clear logging are easier to maintain than clever low-level tricks.
Runnable examples
Submit work to a fixed thread pool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("Task A"));
pool.submit(() -> System.out.println("Task B"));
pool.shutdown();Expected output
Task A Task B
A virtual-thread-per-task executor for blocking-style tasks
import java.util.concurrent.*;
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<String> result = executor.submit(() -> "done");
System.out.println(result.get());
}Expected output
done
Common mistakes
Forgetting to shut down the executor
Always define a shutdown strategy so workers do not linger after the work is done.
Using an unbounded executor strategy without thinking about queue growth
Concurrency control includes limiting how much work the system accepts and how it behaves under pressure.
Sharing one executor for unrelated workloads with very different latency needs
Separate executors by responsibility when one workload could starve another.
Mini exercise
Design a small app that has one scheduled cleanup task and several user-triggered tasks. Choose executor types for both and explain why you would not put them into the same pool by default.
Summary
- Executors manage threads for you.
- Pools control concurrency and reuse worker threads.
- Always shut down executors deliberately.
- Executors are about lifecycle, ownership, and load control, not only convenience.
- Virtual threads are especially useful for many blocking tasks, but measurement still matters.
Next step
After task execution basics, compose async workflows with `CompletableFuture`.
Sources used