Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

Lesson 11: Multithreading — Lifecycle, Synchronization, wait/notify & Executors

Lesson 12 of 18 in the free Programming in Java notes on Siksha Sarovar, written by Rohit Jangra.

11.1 Creating Threads: Thread vs Runnable

Aspectextends Threadimplements Runnable
Inheritance slotConsumed (no other parent possible)Free — can extend another class
DesignTask and thread fusedTask separated from worker (better)
Sharing one task objectAwkwardNatural: many threads, one Runnable
Lambda-friendlyNoYes — Runnable is functional
Thread t = new Thread(() -> System.out.println("run by " + Thread.currentThread().getName()));
t.start();          // NEW thread executes run()
t.run();            // TRAP: ordinary method call on the CURRENT thread!

start() vs run() is the single most-asked question: start() allocates a new call stack and the JVM invokes run() on it; calling run() directly is just a normal method call. Calling start() twice throws IllegalThreadStateException.

11.2 Thread Lifecycle — Six States (Thread.State)

StateMeaningEntered by
NEWCreated, not startednew Thread(r)
RUNNABLERunning or ready, awaiting CPUstart()
BLOCKEDWaiting to acquire a monitor lockContended synchronized
WAITINGWaiting indefinitelywait(), join()
TIMED_WAITINGWaiting with a timeoutsleep(ms), wait(ms), join(ms)
TERMINATEDrun() finished or threw

sleep vs wait: sleep is static on Thread, keeps all locks, needs no monitor; wait is an Object method, releases the monitor, and must be called inside synchronized (else IllegalMonitorStateException).

11.3 Race Conditions & synchronized

count++ is three bytecodes (read, add, write). Two threads interleaving those steps lose updates — with two threads doing 100,000 increments each, the total prints less than 200,000 unpredictably. Fixes:

  • synchronized method — locks this (or the Class object if static).
  • synchronized (lockObj) { ... } block — smaller critical section, custom lock.
  • AtomicInteger.incrementAndGet() — lock-free CAS hardware instruction.
  • volatile guarantees visibility of writes across threads but not atomicity of count++ — a subtle distinction advanced papers love.

Every object owns one intrinsic lock (monitor); a thread may re-acquire a lock it already holds (reentrancy).

11.4 wait / notify — Producer-Consumer

Coordination pattern (guarded block): consumers wait() while the buffer is empty; producers add an item then notify()/notifyAll(). Always wait in a while loop, never an if — threads can wake spuriously, and the condition must be rechecked after reacquiring the lock. Prefer notifyAll() when multiple waiters have different conditions. (Full runnable version in the code snippet.)

11.5 Deadlock — The Classic Two-Lock Story

Thread-1 holds lock A and wants B; Thread-2 holds B and wants A — both wait forever:

// T1: synchronized(A){ synchronized(B){...} }
// T2: synchronized(B){ synchronized(A){...} }   // opposite order => deadlock

Coffman conditions (all four required): mutual exclusion, hold-and-wait, no preemption, circular wait. Prevention: impose a global lock-acquisition order (both threads take A then B), use tryLock with timeout, or shrink to a single lock.

11.6 ExecutorService — Thread Pools (Modern Practice)

Creating raw threads per task is expensive; a pool reuses workers:

ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> f = pool.submit(() -> 6 * 7);     // Callable returns a value
System.out.println(f.get());                       // 42 (blocks until done)
pool.shutdown();                                   // orderly stop — always!

Know: newFixedThreadPool, newCachedThreadPool, newSingleThreadExecutor; Runnable vs Callable (void vs returns value + can throw checked); Future.get() blocks. Also join() (wait for a thread to die), daemon threads (JVM exits when only daemons remain), and thread priorities (1–10, only a hint to the scheduler).

🎯 Exam Focus

  1. Compare creating threads by extending Thread vs implementing Runnable. Why is Runnable preferred?
  2. Draw/describe the six-state thread lifecycle with the methods causing each transition.
  3. Differentiate start() vs run(), sleep() vs wait(), notify() vs notifyAll().
  4. What is a race condition? Write a program showing a lost-update on a shared counter and fix it with synchronized.
  5. Write the producer-consumer program using wait()/notifyAll(), and explain why wait() sits inside a while loop.
  6. What is deadlock? State the four Coffman conditions and two prevention strategies, with a two-lock code sketch.