Advanced
Synchronization and the Executor Framework
📂 Phase 5: Advanced Java — Streams, Lambda, Multithreading, DB (Days 22–26) · JavaBuilding on Day 24's thread basics, this covers inter-thread communication and the modern, recommended way to manage threads in real applications — the Executor Framework, rather than manually creating Thread objects.
Inter-Thread Communication: wait(), notify(), notifyAll()
These methods let threads coordinate — one thread can pause itself until another thread signals that a condition has changed.
class SharedResource {
synchronized void produce() throws InterruptedException {
System.out.println("Producing...");
wait(); // releases the lock and waits to be notified
}
synchronized void consume() {
System.out.println("Consuming...");
notify(); // wakes up one waiting thread
}
}
wait(), notify(), and notifyAll() must always be called from within a synchronized block or method — calling them outside one throws IllegalMonitorStateException.
The Problem with Manual Thread Management
Creating a new Thread object for every task doesn't scale — thread creation is expensive, and an application could easily create thousands of threads under load, exhausting system resources.
The Executor Framework — A Better Approach
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executor = Executors.newFixedThreadPool(4); // pool of 4 reusable threads
for (int i = 1; i <= 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Running task " + taskId + " on " + Thread.currentThread().getName());
});
}
executor.shutdown(); // stops accepting new tasks, finishes existing ones
Why Use a Thread Pool
- Threads are created once and reused across many tasks, rather than created and destroyed repeatedly
- You control the maximum number of concurrent threads, preventing resource exhaustion
- Submitting a task is decoupled from managing the thread itself — you just hand off the work
Common Executor Types
| Factory Method | Behavior |
|---|---|
| newFixedThreadPool(n) | Fixed number of reusable threads |
| newSingleThreadExecutor() | One thread, tasks run sequentially in order |
| newCachedThreadPool() | Creates threads as needed, reuses idle ones |
Interview tip: "Why prefer ExecutorService over manually creating Thread objects?" — Resource control, thread reuse, and a cleaner separation between submitting work and managing how that work actually executes. Production code almost never creates raw Thread objects directly.