⚙️
Multithreading

Advanced Multithreading

Thread Lifecycle & Executors
💡 A thread's lifecycle is like a runner's journey — New (standing at the start line) -> Runnable (ready to run) -> Running (actually running) -> Waiting (catching their breath) -> Terminated (finish line). The Executor Framework is a race manager that automatically decides which runner runs when.

A thread has 5 states: New, Runnable, Running, Waiting/Blocked, Terminated.

wait()/notify() let threads wait for each other's signal. ExecutorService manages a thread pool — instead of manually creating new threads, ready threads get reused.

A deadlock happens when two threads keep waiting for the resource the other one holds, and neither moves forward — like two people at a door, each waiting for the other to go first, forever.

ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("Task running!"));
pool.shutdown();
⚙️
A thread's lifecycle is like a runner's journey — New (standing at the start line) -> Runnable (ready to run) -> Running (actually running) -> Waiting (catching their breath) -> Terminated (finish line). The Executor Framework is a race manager that automatically decides which runner runs when.
1 / 7
⚡ Quick Recap
  • Thread lifecycle: New->Runnable->Running->Waiting->Terminated
  • ExecutorService = a thread pool manager
  • Deadlock = threads stuck waiting forever
On this page (6 subtopics)

A synchronized method (the whole method gets locked) is simple but locks the entire method, even if only a small part is critical — this can cause unnecessary blocking (other threads that want to do unrelated work also have to wait).

A synchronized block (synchronized(obj) { ... }) locks only the necessary part — better performance, since the rest of the code can run in parallel. Every object has its own "monitor lock" that synchronized uses.

synchronized void increment() { count++; } // poora method locked

void increment2() {
  // ... non-critical code, parallel chal sakta hai
  synchronized(this) {
    count++; // sirf ye critical section locked
  }
}
💡Tip: For just a simple counter, using java.util.concurrent.atomic.AtomicInteger instead of synchronized is often better and faster — methods like incrementAndGet() are thread-safe in a lock-free way.

These three are methods on the Object class (available on every object), and can only be used inside a synchronized block (otherwise you get an IllegalMonitorStateException). wait() "puts the current thread to sleep" (and releases the lock) until another thread calls notify().

Classic use-case: the Producer-Consumer pattern, where the consumer calls wait() until the producer produces something and calls notify(). notifyAll() wakes up all waiting threads (not just one), which is safer when multiple threads are waiting.

Every thread goes through 5 states in its "life", starting at New and ending at Terminated. Understanding this matters for debugging — if your thread seems "stuck", checking its state can tell you whether it's in Waiting or Blocked.

StateMeaning
NewThread object created, but start() not called
Runnablestart() called, waiting for/getting CPU time
RunningActually running on the CPU right now
Waiting/BlockedWaiting on wait(), sleep(), or a lock
Terminatedrun() completed, thread finished

Manually creating threads all the time is expensive (every thread costs OS resources). ExecutorService manages a thread pool: newFixedThreadPool(n) — a fixed number of reusable threads, like n workers always ready. newCachedThreadPool() — a pool that grows/shrinks as needed, idle threads get freed once work is done. newScheduledThreadPool() — for running tasks after a delay or on a repeating interval.

ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doTask());
pool.shutdown(); // naye tasks accept karna band, purane complete honge
💡Tip: Always call pool.shutdown() once work is done — otherwise threads may keep running and the program never exits (or resources leak).

Deadlock happens when Thread A holds Lock-1 and waits for Lock-2, while Thread B holds Lock-2 and waits for Lock-1 — both get stuck forever, each waiting for what the other one is holding.

Prevention: always acquire locks in the *same order* across all threads (like always Lock-1 first, then Lock-2, never reversed) — this makes "circular wait" impossible in the first place. Or use locks with timeouts (tryLock(timeout)), so a thread never waits forever and gives up after the timeout.

⚠️Common Mistake: Detecting deadlock in production is very hard (the program just "hangs", no error appears). Maintaining consistent lock ordering is the simplest and most reliable prevention.

In a multi-threaded environment, each thread can have its own CPU-cached "local copy" of a variable (for performance) — so it's possible that one thread's change doesn't immediately show up for another thread.

The volatile keyword guarantees that reads/writes of a variable always go to main memory, not a cache — so one thread's change is immediately visible to other threads. This does NOT give mutual-exclusion like synchronization does (only one thread at a time), it only guarantees "visibility" — for a compound operation like count++, volatile isn't enough, you need synchronized/Atomic.