0% found this document useful (0 votes)
2 views36 pages

ConcurrentProgramming Complete Guide - MD

The document is a comprehensive guide on advanced Java concurrent programming, covering threads, synchronization, and inter-thread communication. It explains the concept of threads, their lifecycle, methods to create them, synchronization techniques to prevent race conditions, and inter-thread communication using wait and notify mechanisms. The document emphasizes best practices and provides code examples for better understanding.

Uploaded by

sernibn8400
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views36 pages

ConcurrentProgramming Complete Guide - MD

The document is a comprehensive guide on advanced Java concurrent programming, covering threads, synchronization, and inter-thread communication. It explains the concept of threads, their lifecycle, methods to create them, synchronization techniques to prevent race conditions, and inter-thread communication using wait and notify mechanisms. The document emphasizes best practices and provides code examples for better understanding.

Uploaded by

sernibn8400
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Advanced Java Concurrent Programming —

Complete Masterclass

Threads, Synchronization, Inter-thread Communication

SECTION 1: WHAT IS A THREAD — THE FOUNDATION

The Real Definition (Not the textbook one)


A thread is a lightweight process that shares the same memory space as other threads in the
same process, but has its own execution path and stack.

Why this matters for your exam:

Most students answer: "A thread is a light process."

You need to answer: "A thread is a lightweight process that runs within a process. Multiple
threads share the heap (memory for objects), but each thread has its own stack (method calls,
local variables). This is why threads can modify shared objects but cannot share local
variables."

Single vs Multi-threading
Single-threaded (Sequential):

Main thread executes Task A → Task B → Task C

Execution: Task A completes → Task B starts → Task C starts

Total time = Time(A) + Time(B) + Time(C)

Multi-threaded (Concurrent):

Thread 1: Task A ———→

Thread 2: Task B ———→

Thread 3: Task C ———→


Execution: All run simultaneously

Total time = Max(Time(A), Time(B), Time(C))

Why Java has Threads — The Real Answer


Not just because "it makes things faster." Because:

1.​ Responsiveness — UI doesn't freeze while downloading data


2.​ Resource utilization — While one thread waits for I/O, others can compute
3.​ Fairness — Multiple users/tasks get CPU time
4.​ Real-world modeling — Real systems have concurrent activities

SECTION 2: THREAD LIFECYCLE — EVERY STATE


EXPLAINED

The 6 States (Not 4 like some textbooks say)


┌──────────────┐

│ NEW │ Thread created but not started

└──────┬───────┘

│ start()

┌──────▼────────────┐

│ RUNNABLE │ Ready to run, waiting for CPU time

└──────┬────────────┘ (Includes both "ready" and "running")

├─ CPU assigns time ──→ Actually executing

└─ waiting for lock ──→ BLOCKED (on monitor lock)


└─ wait() called ──→ WAITING (need notify())

└─ sleep() called ──→ TIMED_WAITING (timeout specified)

└─ I/O waiting ──→ BLOCKED

├─ notify() ──────────────┐

│ or timeout ────────┐ │

│ or spurious wake ──┘ │

│ │

└──────┴───────────────────────────┘

Back to RUNNABLE

│ run() completes or exception

┌──▼──────┐

│ TERMINATED

└─────────┘
Each State in Detail

1. NEW
Thread t = new Thread(() -> [Link]("Hello"));

// Thread is created but NOT started yet

// It hasn't entered the runnable queue

// It's just an object in memory

Exam answer: "A thread in NEW state has been instantiated but start() has not been called. No
resources are allocated yet. It is not competing for CPU time."

2. RUNNABLE
[Link]();

// Now the thread is RUNNABLE

// It may be:

// - Actually executing (running on CPU)

// - Waiting for CPU time (ready)

Critical insight: Java has only RUNNABLE state, not separate "READY" and "RUNNING". The
OS manages which RUNNABLE threads actually get CPU.

Why this matters: If you call isAlive() on a RUNNABLE thread, it returns true. But it might not
be executing right now.

3. BLOCKED
synchronized(lock) {

// Thread 1 enters, gets the lock, does work

// Thread 2 tries to enter the synchronized block


// But Thread 1 still holds the lock

// Thread 2 becomes BLOCKED

// It's waiting for the lock to become available

Key difference from WAITING: BLOCKED is waiting for a monitor lock. No notification needed
— it automatically becomes RUNNABLE when the lock is released.

4. WAITING
synchronized(lock) {

[Link](); // Thread calls wait()

// Now it's in WAITING state

// It has released the lock

// It's waiting for notify() or notifyAll()

Critical: wait() MUST be called inside a synchronized block. If you call it outside, you get
IllegalMonitorStateException.

5. TIMED_WAITING
[Link](5000); // TIMED_WAITING for 5 seconds

[Link](5000); // TIMED_WAITING, wakes up after 5 sec or notify()

Difference from WAITING: Has a timeout. Will automatically become RUNNABLE after timeout
even without notification.

6. TERMINATED
public void run() {

[Link]("Working...");

} // Method ends → Thread is TERMINATED

// Once terminated, a thread cannot be restarted


// Calling start() again throws IllegalThreadStateException

State Transitions Code Example


class ThreadLifecycleDemo {

public static void main(String[] args) throws InterruptedException {

Thread t = new Thread(() -> {

try {

[Link]("Thread in RUNNABLE/RUNNING");

[Link](2000); // TIMED_WAITING

[Link]("After sleep, back to RUNNABLE");

} catch (InterruptedException e) {

[Link]("Thread was interrupted");

});

[Link]("State after creation: " + [Link]()); // NEW

[Link]();

[Link]("State after start(): " + [Link]()); // RUNNABLE

[Link](1000);

[Link]("State during sleep: " + [Link]()); // TIMED_WAITING

[Link](2000);

[Link]("State after completion: " + [Link]()); // TERMINATED

}
}

SECTION 3: CREATING THREADS — THREAD CLASS VS


RUNNABLE

Method 1: Extend Thread Class


class MyThread extends Thread {

@Override

public void run() {

for (int i = 0; i < 5; i++) {

[Link]([Link]().getName() + ": " + i);

// Usage

public static void main(String[] args) {

MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

[Link](); // Starts the thread

[Link]();

}
Method 2: Implement Runnable Interface
class MyRunnable implements Runnable {

@Override

public void run() {

for (int i = 0; i < 5; i++) {

[Link]([Link]().getName() + ": " + i);

// Usage

public static void main(String[] args) {

Thread t1 = new Thread(new MyRunnable());

Thread t2 = new Thread(new MyRunnable());

[Link]();

[Link]();

Method 3: Lambda Expression (Java 8+)


public static void main(String[] args) {

Thread t1 = new Thread(() -> {

for (int i = 0; i < 5; i++) {

[Link]([Link]().getName() + ": " + i);

}
});

[Link]();

CRITICAL: start() vs run()


// WRONG - This does NOT create a new thread

[Link](); // Calls run() in the MAIN thread, not a new thread

// CORRECT - This creates a new thread

[Link](); // JVM creates new thread, then calls run() in that thread

Why this matters: If you call run() directly, it executes in the current thread. Only start() creates
a new thread and calls run() in it.

Thread Class vs Runnable — Which is Better?


Aspect Extend Thread Implement Runnable

Single Inheritance Can't extend another class Can extend another class

Flexibility Limited More flexible

Reusability Less reusable More reusable

Thread Pool Hard to use Easy to use with


ExecutorService

Best Practice Not recommended Recommended

Exam answer: "Implement Runnable is preferred because Java supports single inheritance. If
you extend Thread, you cannot extend any other class. Also, Runnable is easier to use with
thread pools and ExecutorService."
SECTION 4: THREAD SYNCHRONIZATION

The Problem: Race Condition


class Counter {

private int count = 0;

public void increment() {

count++; // This looks like 1 operation but is actually 3:

// 1. Read current value of count

// 2. Add 1

// 3. Write back to count

// Now two threads call increment() simultaneously

Thread t1 = new Thread(() -> {

for (int i = 0; i < 1000; i++) [Link]();

});

Thread t2 = new Thread(() -> {

for (int i = 0; i < 1000; i++) [Link]();

});

[Link]();

[Link]();
[Link]();

[Link]();

[Link]("Expected: 2000, Actual: " + count);

// Output might be: 1847 (not 2000!)

Why?

Timeline of operations (simplified):

Time Thread1 Thread2

1 Read count (0)

2 Read count (0)

3 Add 1, Write (1)

4 Add 1, Write (1)

5 Read count (1)

...

Both threads read 0, both add 1, both write 1.

But we expected 2. This is a race condition.

Solution 1: Synchronized Method


class Counter {

private int count = 0;

public synchronized void increment() {

count++;
}

public synchronized int getCount() {

return count;

What synchronized does:

1.​ Only ONE thread can execute this method at a time


2.​ When a thread calls a synchronized method, it acquires the lock (monitor) of the object
3.​ Other threads wait for the lock to be released
4.​ When the method exits, the lock is released

Lock is on the OBJECT, not the method:

Counter c1 = new Counter();

Counter c2 = new Counter();

Thread t1 = new Thread(() -> [Link]()); // Locks c1

Thread t2 = new Thread(() -> [Link]()); // Locks c2

// t1 and t2 run simultaneously!

// Each object has its own lock

// They are not competing for the same lock

Solution 2: Synchronized Block


class Counter {

private int count = 0;

private Object lock = new Object();


public void increment() {

synchronized(lock) {

count++;

Advantages over synchronized method:

1.​ Fine-grained control — Only lock the critical section


2.​ Multiple locks — Can have different locks for different resources
3.​ Performance — Non-critical code doesn't hold the lock

Solution 3: More Control with Lock Interface


import [Link].*;

class Counter {

private int count = 0;

private Lock lock = new ReentrantLock();

public void increment() {

[Link]();

try {

count++;

} finally {

[Link]();
}

Why try-finally? If an exception occurs, the lock must still be released. try-finally ensures it
happens.

Static Synchronized Methods


class SharedResource {

private static int sharedCounter = 0;

public static synchronized void increment() {

sharedCounter++;

Important: The lock is on the CLASS object ([Link]), not on an instance.

Counter c1 = new Counter();

Counter c2 = new Counter();

Thread t1 = new Thread(() -> [Link]()); // Locks [Link]

Thread t2 = new Thread(() -> [Link]()); // Waits for [Link] lock

// Only ONE thread executes at a time across all instances

Reentrant Lock
class Example {

private Lock lock = new ReentrantLock();


public void method1() {

[Link]();

try {

[Link]("In method1");

method2(); // SAME thread can acquire the SAME lock again

} finally {

[Link]();

public void method2() {

[Link]();

try {

[Link]("In method2");

} finally {

[Link]();

Why "Reentrant"? The same thread can reacquire the same lock multiple times. Without
reentrance, this would deadlock (thread waiting for itself).
SECTION 5: INTER-THREAD COMMUNICATION — wait(),
notify(), notifyAll()

The Problem: Producer-Consumer


class Buffer {

private int value;

private boolean hasValue = false;

// Producer puts data

public void put(int val) {

[Link] = val;

[Link] = true;

// Consumer gets data

public int get() {

if (!hasValue) {

return -1; // No data available (INEFFICIENT)

return value;

}
Problem: Consumer keeps polling (checking hasValue again and again). This wastes CPU. We
need a way for threads to communicate.

The Solution: wait(), notify(), notifyAll()


class Buffer {

private int value;

private boolean hasValue = false;

public synchronized void put(int val) {

while (hasValue) {

try {

wait(); // Producer waits if buffer already has value

} catch (InterruptedException e) {

[Link]().interrupt();

[Link] = val;

[Link] = true;

notify(); // Wake up the consumer

public synchronized int get() {

while (!hasValue) {
try {

wait(); // Consumer waits if no value available

} catch (InterruptedException e) {

[Link]().interrupt();

int val = [Link];

[Link] = false;

notify(); // Wake up the producer

return val;

Why while, not if?

// WRONG:

if (!hasValue) wait();

// CORRECT:

while (!hasValue) wait();

// Reason: After notify(), the thread wakes up but MUST recheck the condition

// Because:

// 1. Another thread might have grabbed the resource before this thread resumes

// 2. Spurious wakeups can happen (thread wakes without explicit notification)


// 3. Multiple threads might be waiting; when notified, you must verify your condition

wait(), notify(), notifyAll() Rules


RULE 1: Must be called from synchronized context

// WRONG - throws IllegalMonitorStateException

void someMethod() {

[Link](); // ERROR!

// CORRECT

synchronized void someMethod() {

[Link](); // OK

// ALSO CORRECT

void someMethod() {

synchronized(lock) {

[Link](); // OK

RULE 2: wait() releases the lock

synchronized void example() {

// At this point, thread holds the lock

wait();
// wait() releases the lock and waits for notification

// Other threads can now enter synchronized methods/blocks

// After notification, thread reacquires the lock before continuing

RULE 3: notify() wakes ONE waiting thread (arbitrary choice)

synchronized void notify() {

notify(); // Wakes exactly ONE waiting thread

// Which one? JVM decides (unpredictable)

// Problem: If you have both producers and consumers waiting,

// notify() might wake a producer when a consumer needed waking

RULE 4: notifyAll() wakes ALL waiting threads

synchronized void example() {

notifyAll(); // Wakes ALL waiting threads

// All compete to reacquire the lock

// They check their conditions and proceed accordingly

// Better for safety: All threads wake and check their conditions

Complete Producer-Consumer Example


class Buffer {
private int[] data = new int[5];

private int count = 0;

private int in = 0; // Insert position

private int out = 0; // Remove position

public synchronized void produce(int value) {

while (count == 5) { // Buffer is full

try {

[Link]("Buffer full, producer waiting...");

wait();

} catch (InterruptedException e) {

[Link]().interrupt();

data[in] = value;

in = (in + 1) % 5;

count++;

[Link]("Produced: " + value + ", Buffer size: " + count);

notifyAll(); // Notify any waiting consumers

}
public synchronized int consume() {

while (count == 0) { // Buffer is empty

try {

[Link]("Buffer empty, consumer waiting...");

wait();

} catch (InterruptedException e) {

[Link]().interrupt();

int value = data[out];

out = (out + 1) % 5;

count--;

[Link]("Consumed: " + value + ", Buffer size: " + count);

notifyAll(); // Notify any waiting producers

return value;

// Usage

public static void main(String[] args) {

Buffer buffer = new Buffer();


Thread producer = new Thread(() -> {

for (int i = 0; i < 10; i++) {

[Link](i);

try { [Link](500); } catch (InterruptedException e) {}

});

Thread consumer = new Thread(() -> {

for (int i = 0; i < 10; i++) {

[Link]();

try { [Link](1000); } catch (InterruptedException e) {}

});

[Link]();

[Link]();

}
SECTION 6: REAL-WORLD PATTERNS

Pattern 1: Thread Pool with ExecutorService


import [Link].*;

public class ThreadPoolExample {

public static void main(String[] args) {

// Create a thread pool with 5 threads

ExecutorService executor = [Link](5);

for (int i = 0; i < 10; i++) {

final int taskId = i;

[Link](() -> {

[Link]("Task " + taskId + " running in " +

[Link]().getName());

try { [Link](2000); } catch (InterruptedException e) {}

});

[Link](); // No new tasks accepted

[Link](10, [Link]); // Wait for completion

}
Pattern 2: Volatile for Simple Flags
class VolatileExample {

private volatile boolean flag = false; // Changes are visible across threads

public void setFlag(boolean value) {

flag = value;

public void waitForFlag() {

while (!flag) {

[Link](); // Efficient waiting for flag

// When to use volatile instead of synchronized:

// - Single variable

// - Only reads and writes (no compound operations)

// - Performance-critical code

Pattern 3: CountDownLatch
import [Link].*;

public class CountDownLatchExample {

public static void main(String[] args) throws InterruptedException {


int taskCount = 5;

CountDownLatch latch = new CountDownLatch(taskCount);

for (int i = 0; i < taskCount; i++) {

new Thread(() -> {

[Link]("Task running...");

try { [Link](1000); } catch (InterruptedException e) {}

[Link](); // Signal completion

}).start();

[Link](); // Main thread waits for all tasks to complete

[Link]("All tasks completed!");

SECTION 7: EXAM QUESTIONS — ANSWERED AT DEPTH

Q1: What is the difference between Thread class and Runnable interface?
Surface answer: "Thread class extends Thread, Runnable implements Runnable."

Depth answer: "Thread class and Runnable interface are two ways to create threads:

1.​ Inheritance vs Interface:


-​ Thread: Extends Thread class (single inheritance limitation)
-​ Runnable: Implements Runnable interface (can extend another class)

2.​ Flexibility:

-​ Thread: If you need to extend another class, you can't use Thread
-​ Runnable: More flexible, recommended for new code

3.​ Usage with Thread Pools:

-​ Thread: Hard to use with ExecutorService


-​ Runnable: Designed for use with thread pools

4.​ Best Practice:

-​ Runnable is the recommended approach because of Java's single inheritance


constraint. Even though Thread implements Runnable, the industry standard is to
implement Runnable directly.

Example:

// Limited - can't extend another class

class MyThread extends Thread { }

// Flexible - can extend any class

class MyRunnable extends SomeOtherClass implements Runnable { }

5.​ Performance:
-​ No meaningful difference
-​ The real difference is architectural"

Q2: Explain the lifecycle of a thread with all states.


Surface answer: "A thread has 4 states: new, runnable, blocked, terminated."

Depth answer: "A thread has 6 states in Java:

1.​ NEW: Created but not started

-​ Thread t = new Thread(...) → thread is NEW


-​ No resources allocated
-​ getState() returns [Link]

2.​ RUNNABLE: Started and ready to run

-​ [Link]() → thread becomes RUNNABLE


-​ Includes both 'ready to run' and 'actually running'
-​ Java doesn't distinguish between them
-​ CPU scheduler decides when it actually gets CPU time

3.​ BLOCKED: Waiting for monitor lock

-​ When thread tries to enter synchronized block/method but lock is held


-​ getState() returns [Link]
-​ Automatically becomes RUNNABLE when lock is released

4.​ WAITING: Waiting for notification

-​ [Link]() → thread enters WAITING


-​ Thread has released its lock
-​ Waits indefinitely for notify() or notifyAll()
-​ getState() returns [Link]

5.​ TIMED_WAITING: Waiting with timeout

-​ [Link](ms) → TIMED_WAITING
-​ [Link](ms) → TIMED_WAITING
-​ Will automatically return to RUNNABLE after timeout
-​ OR when notify() is called (whichever comes first)

6.​ TERMINATED: Execution completed

-​ run() method completed or threw exception


-​ Thread is dead, cannot be restarted
-​ Calling start() again throws IllegalThreadStateException

State transitions:

NEW → start() → RUNNABLE

(CPU time)

↙wait() ↓ lock unavailable↘

WAITING RUNNABLE BLOCKED

↘ ↗ notify()↗

RUNNABLE

run() ends

TERMINATED

Key insight: The JVM schedules RUNNABLE threads. We can't control which RUNNABLE
thread gets CPU time. That's the OS scheduler's job."

Q3: What happens when you call wait() without synchronized?


Surface answer: "It throws an error."

Depth answer: "Calling wait() outside synchronized context throws


IllegalMonitorStateException.

Why?

Wait() is designed to work with the monitor lock system:

1.​ When you call wait(), the thread releases the lock it holds
2.​ Without synchronized context, there is NO lock to release
3.​ The JVM cannot enforce the lock/release mechanism
4.​ Therefore, it throws IllegalMonitorStateException

Example:

// WRONG:
void wrongMethod() {

[Link](); // IllegalMonitorStateException

// CORRECT:

synchronized void rightMethod() {

[Link](); // OK

// ALSO CORRECT:

void anotherRight() {

synchronized(lock) {

[Link](); // OK

Why this design?

Wait() needs a lock for three reasons:

1.​ Atomicity: Release lock and wait must be atomic (one operation)
2.​ Condition checking: You must hold the lock while checking the condition and calling
wait()
3.​ Wake-up safety: notify() requires the waiting thread to reacquire the same lock

If wait() could be called without synchronized, these guarantees would break."

Q4: What is the difference between notify() and notifyAll()?


Surface answer: "notify() wakes one thread, notifyAll() wakes all."

Depth answer: "Both wake waiting threads, but in different ways:


notify():

synchronized void produce() {

// ... produce item ...

notify(); // Wakes exactly ONE arbitrary waiting thread

-​ Wakes one arbitrary waiting thread (JVM chooses which one)


-​ More efficient (less context switching)
-​ Problem: If both producers and consumers are waiting, notify() might wake the wrong
type
-​ Use when: Exactly one thread should be woken

notifyAll():

synchronized void produce() {

// ... produce item ...

notifyAll(); // Wakes ALL waiting threads

-​ Wakes every thread waiting on this object


-​ All threads reacquire the lock and proceed
-​ They check their conditions; if not met, they wait again
-​ Safer: All threads get a chance to check their condition
-​ Use when: You're unsure which thread should wake or multiple threads need waking

Example of problem with notify():

class ProducerConsumer {

private int value;

private boolean full = false;


synchronized void produce(int val) {

while (full) wait();

value = val;

full = true;

notify(); // PROBLEM: Might wake another producer!

// If another producer wakes, it will wait() again

synchronized int consume() {

while (!full) wait();

int val = value;

full = false;

notify(); // PROBLEM: Might wake another consumer!

// If another consumer wakes, it will wait() again

// If only one producer and one consumer: notify() works fine

// If multiple producers/consumers: notifyAll() is safer

Best Practice: Use notifyAll() unless you have a specific reason to use notify(). The
performance difference is negligible, but safety is paramount in concurrent programming."

Q5: Why use while instead of if with wait()?


Surface answer: "Because the condition might change."
Depth answer: "You MUST use while, not if, when calling wait():

// WRONG:

synchronized void wrong() {

if (!condition) wait();

// Use resource

// CORRECT:

synchronized void right() {

while (!condition) wait();

// Use resource

Why?

Three reasons:

1.​ Spurious Wakeups: A thread can wake from wait() without explicit notification. This is
rare but legal:

synchronized void spuriousWakeup() {

while (!condition) wait(); // If woken spuriously, loop checks condition again

// By the time we get here, condition is definitely true

2.​ Multiple Waiting Threads: When notifyAll() is called, all threads wake:

synchronized void buggyVersion() {

if (!condition) wait();
// Thread 1 wakes, uses resource

// Thread 2 wakes, BUT CONDITION IS NOW FALSE

// It proceeds anyway - BUG!

synchronized void fixedVersion() {

while (!condition) wait();

// Thread 1 wakes, uses resource, sets condition = false

// Thread 2 wakes, checks condition (false), waits again

// Correct behavior

3.​ Lost Notification: Race condition between notify and wait:

// Timeline:

T1: Check condition (true) → about to wait()

T2: Change condition to false

T2: notify() // T1 hasn't called wait() yet!

T1: wait() // Wait for notification that already happened - DEADLOCK!

// With while:

T1: while (condition) wait();

T1: Check condition (true)

T2: Change condition to false

T2: notify()

T1: wait() // Wakes immediately (was already true before wait)


T1: Check condition (false), exit loop - CORRECT!

Rule: Always use while(condition) wait(), never if(condition) wait()."

FINAL EXAM CHECKLIST


Before the exam, make sure you can answer these at depth:

​ What is a thread vs process?


​ All 6 thread states and transitions
​ Why start() not run()
​ Thread vs Runnable with specific examples
​ Race condition and why it happens
​ How synchronized works (lock acquired/released)
​ Difference between synchronized method and block
​ Why wait() needs synchronized context
​ while vs if with wait()
​ notify() vs notifyAll() with examples
​ Producer-consumer pattern end-to-end
​ Reentrant locks and why "reentrant"
​ Real-world patterns (thread pools, executors)

REMEMBER
You are not just trying to pass the exam. You are trying to answer so deeply that the examiner
realizes you understand not just WHAT threads do, but WHY they work that way.

Every answer should be:

1.​ Technically correct


2.​ Explain the WHY (not just what)
3.​ Include code examples
4.​ Mention edge cases or common mistakes
5.​ Connect to real-world usage

This depth will separate your answers from every other student in that room.
You've got this. Now go master it.

You might also like