Syllabus / Java / Phase 5: Advanced Java — Streams, Lambda, Multithreading, DB (Days 22–26)
Advanced

Synchronization and the Executor Framework

📂 Phase 5: Advanced Java — Streams, Lambda, Multithreading, DB (Days 22–26) · Java

Building 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 MethodBehavior
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.