Chapter 17: Multithreading

In 2026, almost every serious application (web servers, Android apps, big data processing, games, banking systems) uses multithreading to do many things at the same time — download a file while showing progress bar, handle 1000 user requests simultaneously, process data in background without freezing the UI, etc.

We’ll go super slowly, like we’re sitting together in a quiet Bandra café with the sea breeze — I’ll explain every concept step by step with real-life analogies, complete runnable programs, detailed breakdowns, tables, common mistakes with fixes, and tons of examples you can copy-paste and run right now.

Let’s dive in!

1. What is Multithreading? (The Big Idea)

Multithreading means multiple threads (lightweight subprocesses) running concurrently inside the same process (same program).

  • Process = heavy, has its own memory
  • Thread = lightweight, shares the same memory with other threads in the process

Real-life analogy: You are cooking dinner (single-threaded):

  • Boil water → wait → chop vegetables → wait → fry → wait Everything is sequential — slow!

Multithreading:

  • One thread boils water
  • Another chops vegetables
  • Another fries onions All happening at the same time → dinner ready much faster!

Benefits:

  • Better performance (use multiple CPU cores)
  • Responsiveness (UI doesn’t freeze while doing heavy work)
  • Parallel processing (download + process + update UI)

Risks:

  • Race conditions
  • Deadlocks
  • Thread interference

2. Two Ways to Create Threads in Java

Way 1: Extending the Thread class

Java

Output (order may vary — threads run concurrently):

text

Way 2: Implementing the Runnable interface (Recommended)

Java

Why prefer Runnable?

  • You can extend another class (Java doesn’t allow multiple inheritance)
  • Better separation of task (Runnable) and thread (Thread)

3. Thread Lifecycle (The 6 States)

State Description How to reach / leave
NEW Thread created but not started (new Thread()) Call start() → RUNNABLE
RUNNABLE Ready to run or actually running Gets CPU time or yields → stays RUNNABLE
BLOCKED Waiting for a monitor lock (synchronized block) Acquires lock → RUNNABLE
WAITING Waiting indefinitely (wait(), join(), LockSupport.park()) notify(), notifyAll(), interrupt(), timeout → RUNNABLE
TIMED_WAITING Waiting with timeout (sleep(), wait(timeout), join(timeout)) Time up, notify, interrupt → RUNNABLE
TERMINATED Thread finished execution or exception thrown End of run() method

Visual:

text

4. Synchronization – Preventing Race Conditions

When multiple threads access shared data, you get race conditions → wrong results.

Example without synchronization (wrong balance!):

Java

Fix: Synchronization

  1. Synchronized method
Java
  1. Synchronized block (better – smaller scope)
Java
  1. Using volatile (only visibility, not atomicity)
Java

5. Locks (More Flexible Synchronization – Java 5+)

ReentrantLock gives more control than synchronized.

Java

6. Thread Pools & ExecutorService (Modern & Recommended Way)

Manually creating 1000 threads is bad — too much overhead.

ExecutorService + ThreadPool reuses threads.

Example: Using Fixed Thread Pool

Java

Common Executors:

  • Executors.newFixedThreadPool(n) → fixed number of threads
  • Executors.newCachedThreadPool() → creates threads as needed, reuses idle ones
  • Executors.newSingleThreadExecutor() → only one thread (like sequential)
  • Executors.newScheduledThreadPool() → for scheduled tasks

Quick Recap Table (Your Cheat Sheet)

Concept Key Points / Best Practice Example
Create Thread Prefer implements Runnable new Thread(runnable).start();
Thread Lifecycle NEW → RUNNABLE → BLOCKED/WAITING → TERMINATED start(), sleep(), join()
Synchronization Use synchronized or Lock on shared data synchronized (obj) { … }
volatile Ensures visibility, not atomicity private volatile boolean flag;
Thread Pool Use ExecutorService – never create 1000s of threads Executors.newFixedThreadPool(10)
join() Wait for thread to finish t1.join();
sleep() vs wait() sleep() → static, hold lock; wait() → release lock

Common Mistakes & Fixes

Mistake Problem Fix
Call run() instead of start() Runs in main thread – no multithreading Always call start()
No finally { lock.unlock(); } Deadlock – lock never released Always release lock in finally
Accessing shared data without sync Race condition – wrong results Use synchronized or Lock
Not shutting down ExecutorService Program hangs Call executor.shutdown()
Catching InterruptedException silently Thread may not stop properly Restore interrupt: Thread.currentThread().interrupt();

Homework for You (Practice to Master!)

  1. Basic: Create 5 threads using Runnable — each prints numbers 1 to 10 with 200ms delay.
  2. Medium: Create a shared Counter class. Increment it 10000 times from 3 threads. Fix race condition using synchronized.
  3. Advanced: Use ExecutorService with 4 threads to process 20 tasks (print task ID + thread name).
  4. Fun: Create two threads: one prints “Tic” every 1s, another prints “Tac” every 1s — they should alternate.
  5. Challenge: Implement a thread-safe Singleton class using double-checked locking.

You’re doing amazing! Multithreading is the heart of modern Java applications — now you can write fast, concurrent, real-world code.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *