Skip to main content

Command Palette

Search for a command to run...

JAVA-00-Multithreading

Published
β€’31 min readβ€’View as Markdown

πŸ”₯ Excellent choice β€” Multithreading is one of the most powerful and frequently asked topics in Core Java interviews, especially for automation testers, backend, and performance testing roles.

Let’s go step-by-step from basic β†’ advanced, with clear examples, diagrams, and interview-level concepts πŸ‘‡


🧩 1️⃣ What is Multithreading?

🧠 Definition:
Multithreading means executing multiple threads (smallest unit of a process) simultaneously to perform tasks concurrently and improve CPU efficiency.

πŸ’‘ Think of it as:

"Doing multiple things at the same time within the same program."


βš™οΈ 2️⃣ Key Concepts

TermDescription
ProcessAn independent program in execution (has its own memory).
ThreadA lightweight sub-process inside a process β€” shares memory and resources with other threads.
MultitaskingRunning multiple processes at once (e.g., browser + IDE + music).
MultithreadingRunning multiple threads of the same process (e.g., browser loading multiple tabs).

🧩 3️⃣ Advantages of Multithreading

βœ… Improves performance by parallel execution
βœ… Better CPU utilization
βœ… Non-blocking β€” one thread can work while others wait
βœ… Great for I/O tasks (e.g., file download + UI update)
βœ… Useful in Selenium automation, web servers, data processing


🧩 4️⃣ Thread Life Cycle (Diagram + Explanation)

     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚   New        β”‚  (Thread created)
     β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ start()
           β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Runnable     β”‚  (Ready to run)
     β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ CPU schedules
           β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Running      β”‚  (Executing code)
     β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ sleep()/wait()/yield()
           β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Blocked/Wait β”‚  (Temporarily inactive)
     β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ CPU resumes
           β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Terminated   β”‚  (Finished)
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧩 5️⃣ How to Create Threads in Java

There are two main ways:


1️⃣ By Extending the Thread class

class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - Count: " + i);
        }
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("Thread A");
        t2.setName("Thread B");

        t1.start(); // Starts thread (calls run() internally)
        t2.start();
    }
}

Output (order may vary):

Thread A - Count: 1
Thread B - Count: 1
Thread A - Count: 2
Thread B - Count: 2
...

2️⃣ By Implementing Runnable Interface

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(Thread.currentThread().getName() + " running step " + i);
        }
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable(), "Worker-1");
        Thread t2 = new Thread(new MyRunnable(), "Worker-2");

        t1.start();
        t2.start();
    }
}

πŸ’‘ Note:
Implementing Runnable is preferred because Java supports multiple interfaces but only single inheritance.


🧩 6️⃣ Thread Methods

MethodDescription
start()Starts a new thread and calls run()
run()The code executed by the thread
sleep(ms)Pauses execution for given milliseconds
join()Waits for another thread to finish
yield()Suggests thread scheduler to give chance to other threads
isAlive()Checks if thread is still running
setName() / getName()Sets/gets thread name
setPriority(int)Sets thread priority (1–10)
currentThread()Returns the current thread reference

🧩 7️⃣ Example β€” join() and sleep()

class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(getName() + " - " + i);
            try {
                Thread.sleep(500); // pauses for 0.5s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class JoinSleepDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("Thread-1");
        t2.setName("Thread-2");

        t1.start();
        t1.join(); // main waits for t1 to finish
        t2.start();

        System.out.println("Main thread finished.");
    }
}

🧩 8️⃣ Thread Priority

Each thread has a priority (1–10) that helps the CPU decide which one to run first.

ConstantValueDescription
Thread.MIN_PRIORITY1Lowest
Thread.NORM_PRIORITY5Default
Thread.MAX_PRIORITY10Highest

Example:

t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);

⚠️ Priority doesn’t guarantee order β€” depends on OS thread scheduler.


🧩 9️⃣ Thread Synchronization

πŸ’‘ When multiple threads access a shared resource, you can get inconsistent results.
To prevent that β†’ use synchronization.

Without synchronization:

class Counter {
    int count = 0;

    void increment() {
        count++;
    }
}

If multiple threads call increment(), they may interfere and skip values.


With synchronization:

class Counter {
    int count = 0;

    synchronized void increment() {
        count++;
    }
}

public class SyncDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final Count: " + counter.count);
    }
}

βœ… Output:

Final Count: 2000

🧩 πŸ”Ÿ Types of Synchronization

TypeKeyword/ConceptUsed For
Method synchronizationsynchronized keyword on methodLocks whole method
Block synchronizationsynchronized(obj)Locks only block
Static synchronizationsynchronized staticLocks class level
Inter-thread communicationwait(), notify(), notifyAll()Thread coordination

🧩 11️⃣ Inter-Thread Communication Example

class Shared {
    synchronized void printEven(int n) {
        System.out.println("Even: " + n);
        notify(); // wake up other thread
        try { wait(); } catch (Exception e) {}
    }

    synchronized void printOdd(int n) {
        System.out.println("Odd: " + n);
        notify();
        try { wait(); } catch (Exception e) {}
    }
}

(This concept is often used in producer–consumer problems.)


🧩 12️⃣ Daemon Threads

Daemon threads run in the background (like garbage collector).
When all user threads finish, JVM kills daemon threads.

Thread t = new Thread(() -> System.out.println("Daemon Running"));
t.setDaemon(true);
t.start();

🧩 13️⃣ Thread Pooling (Executor Framework – Java 5+)

Instead of creating many threads manually, use ExecutorService.

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 1; i <= 5; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("Task " + taskId + " executed by " + Thread.currentThread().getName());
            });
        }

        executor.shutdown();
    }
}

βœ… Output:

Task 1 executed by pool-1-thread-1
Task 2 executed by pool-1-thread-2
...

🧩 14️⃣ Concurrency Utilities (Advanced Java)

PackageFeatureDescription
java.util.concurrentExecutorsThread pools
Future, CallableReturn results from threads
CountDownLatch, CyclicBarrierThread coordination
LockAdvanced locking mechanism
AtomicInteger, etc.Thread-safe numeric operations

🧩 15️⃣ Real-World Example (Selenium Context)

βœ… Parallel Test Execution with TestNG:

<suite name="ParallelSuite" parallel="tests" thread-count="2">
  <test name="ChromeTest">...</test>
  <test name="FirefoxTest">...</test>
</suite>

β†’ Runs multiple browsers in parallel threads.

βœ… Asynchronous tasks like file uploads, report generation, logging β€” all use threading.


🧩 16️⃣ Summary Table

ConceptDescriptionKeyword/Class
Create ThreadExtend Thread or implement RunnableThread, Runnable
Run ThreadStart new threadstart()
PauseSleep threadsleep()
Wait for anotherWait for thread to finishjoin()
Sync accessPrevent data conflictsynchronized
Background taskLow-priority daemon threadsetDaemon(true)
Thread poolManage reusable threadsExecutorService
Return resultCallable with FutureFuture<T>
Safe shared variableAtomic variableAtomicInteger

🧩 17️⃣ Diagram β€” Thread Relationships

 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚   Thread       β”‚
 β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
 β”‚ + start()      β”‚
 β”‚ + run()        β”‚
 β”‚ + sleep()      β”‚
 β”‚ + join()       β”‚
 β”‚ + isAlive()    β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β–²
        β”‚ implements
        β”‚
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  Runnable     β”‚
 β”‚ + run()       β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Would you like me to continue next with advanced multithreading concepts (like Callable, Future, ThreadPoolExecutor, synchronized vs Lock, and real interview questions with answers)?

You said:

yes

ChatGPT said:

Perfect βš‘β€” now we’ll move into Advanced Multithreading in Java β€” this is where real interview and project-level concepts come in.
We’ll go through:
βœ… Callable and Future
βœ… ExecutorService and ThreadPoolExecutor
βœ… Lock vs synchronized
βœ… Atomic classes
βœ… CountDownLatch, CyclicBarrier, Semaphore
βœ… Real-world automation example
βœ… Interview questions


🧩 1️⃣ Callable and Future β€” Return Values from Threads

The normal Runnable interface cannot return a value.
To get a result from a thread, use Callable<T>.


βœ… Example

import java.util.concurrent.*;

class MyCallable implements Callable<Integer> {
    private int number;

    MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + " calculating sum...");
        int sum = 0;
        for (int i = 1; i <= number; i++) sum += i;
        Thread.sleep(500);
        return sum; // returns the result
    }
}

public class CallableFutureDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Future<Integer> f1 = executor.submit(new MyCallable(5));
        Future<Integer> f2 = executor.submit(new MyCallable(10));

        System.out.println("Sum from Thread 1: " + f1.get());
        System.out.println("Sum from Thread 2: " + f2.get());

        executor.shutdown();
    }
}

Output:

pool-1-thread-1 calculating sum...
pool-1-thread-2 calculating sum...
Sum from Thread 1: 15
Sum from Thread 2: 55

πŸ’‘ Future.get() blocks until the result is ready.


🧩 2️⃣ ExecutorService and ThreadPoolExecutor

🧠 Why we need it:

Creating too many threads manually = resource waste.
So Java introduced the Executor Framework to manage thread pools efficiently.


βœ… Example β€” Fixed Thread Pool

import java.util.concurrent.*;

public class ExecutorServiceDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 1; i <= 6; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("Task " + taskId + " running in " + Thread.currentThread().getName());
            });
        }

        executor.shutdown();
    }
}

Output (order may vary):

Task 1 running in pool-1-thread-1
Task 2 running in pool-1-thread-2
Task 3 running in pool-1-thread-3
Task 4 running in pool-1-thread-1
Task 5 running in pool-1-thread-2
Task 6 running in pool-1-thread-3

🧰 ThreadPool Types

MethodDescription
newFixedThreadPool(n)Fixed number of threads
newCachedThreadPool()Creates threads as needed
newSingleThreadExecutor()Only one thread
newScheduledThreadPool(n)For delayed/repeated tasks

βœ… Example β€” Scheduled Thread Execution

import java.util.concurrent.*;

public class ScheduledDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        scheduler.scheduleAtFixedRate(() ->
            System.out.println("Task executed at " + System.currentTimeMillis()),
            0, 2, TimeUnit.SECONDS);
    }
}

🧩 3️⃣ Lock vs synchronized

FeaturesynchronizedLock (java.util.concurrent.locks)
SimplicityEasier to useMore flexible
ReentrantYesYes
Try to acquireNot possiblePossible (tryLock())
TimeoutNot possiblePossible
FairnessNot possiblePossible (ReentrantLock(true))
PerformanceModerateBetter for heavy concurrency

βœ… Example β€” ReentrantLock

import java.util.concurrent.locks.*;

class PrintJob implements Runnable {
    private final Lock lock = new ReentrantLock();

    public void run() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " printing...");
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + " done.");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class LockDemo {
    public static void main(String[] args) {
        PrintJob job = new PrintJob();
        Thread t1 = new Thread(job, "Thread-A");
        Thread t2 = new Thread(job, "Thread-B");
        t1.start();
        t2.start();
    }
}

🧩 4️⃣ Atomic Classes

Used for thread-safe arithmetic without synchronization.

ClassDescription
AtomicIntegerInteger operations
AtomicBooleanBoolean
AtomicLongLong
AtomicReferenceReference objects

βœ… Example

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                count.incrementAndGet();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final Count: " + count.get());
    }
}

βœ… Output:

Final Count: 2000

🧩 5️⃣ CountDownLatch

Used when one thread must wait for others to complete.


βœ… Example

import java.util.concurrent.*;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        for (int i = 1; i <= 3; i++) {
            int taskId = i;
            new Thread(() -> {
                System.out.println("Task " + taskId + " finished.");
                latch.countDown();
            }).start();
        }

        latch.await(); // waits until count = 0
        System.out.println("All tasks completed. Continue main thread.");
    }
}

🧩 6️⃣ CyclicBarrier

Used when multiple threads must wait for each other to reach a point before continuing.


βœ… Example

import java.util.concurrent.*;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("All threads reached barrier β€” continue together!");
        });

        for (int i = 1; i <= 3; i++) {
            int id = i;
            new Thread(() -> {
                System.out.println("Thread " + id + " waiting...");
                try {
                    barrier.await();
                    System.out.println("Thread " + id + " proceeding...");
                } catch (Exception e) {}
            }).start();
        }
    }
}

🧩 7️⃣ Semaphore

Used to limit access to a shared resource (like a parking lot with limited spots).


βœ… Example

import java.util.concurrent.*;

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore sem = new Semaphore(2); // only 2 threads allowed

        for (int i = 1; i <= 5; i++) {
            int car = i;
            new Thread(() -> {
                try {
                    sem.acquire();
                    System.out.println("Car " + car + " parked.");
                    Thread.sleep(1000);
                    System.out.println("Car " + car + " leaving.");
                    sem.release();
                } catch (Exception e) {}
            }).start();
        }
    }
}

🧩 8️⃣ Real-World Example (Selenium + Thread Pool)

import java.util.concurrent.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

class BrowserTask implements Callable<String> {
    private String url;
    BrowserTask(String url) { this.url = url; }

    public String call() {
        WebDriver driver = new ChromeDriver();
        driver.get(url);
        String title = driver.getTitle();
        driver.quit();
        return title;
    }
}

public class ParallelSelenium {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        Future<String> f1 = executor.submit(new BrowserTask("https://www.google.com"));
        Future<String> f2 = executor.submit(new BrowserTask("https://www.bing.com"));

        System.out.println("Google title: " + f1.get());
        System.out.println("Bing title: " + f2.get());
        executor.shutdown();
    }
}

πŸ’‘ This is how parallel test execution happens internally in TestNG.


🧩 9️⃣ Common Interview Questions

QuestionAnswer
What is a thread in Java?A lightweight sub-process for concurrent execution.
Difference between Runnable and Thread?Runnable is an interface; Thread is a class. Prefer Runnable for flexibility.
What is thread safety?When code behaves correctly when accessed by multiple threads.
What are daemon threads?Background threads that stop when main thread finishes.
What is synchronization?Controlling access to shared resources.
Difference between synchronized and Lock?Lock provides more flexibility (tryLock, fairness).
What is deadlock?Two or more threads waiting forever for each other’s lock.
How to prevent deadlock?Acquire locks in same order, use timeout or tryLock.
What is a race condition?When multiple threads modify shared data unpredictably.
What are Callable and Future?Callable returns a result; Future holds it.

🧩 πŸ”Ÿ Summary

ConceptKey ClassDescription
Basic ThreadThread, RunnableCreate & run threads
Return valueCallable, FutureThread result handling
Thread poolExecutorServiceManage multiple threads
SchedulingScheduledExecutorServiceTimed or repeated tasks
Synchronizationsynchronized, LockThread safety
CoordinationCountDownLatch, CyclicBarrierThread sync
Control accessSemaphoreLimit concurrent threads
Atomic opsAtomicInteger, etc.Non-blocking operations

Would you like me to continue next with
πŸ‘‰ Thread synchronization problems (deadlock, race condition, starvation, producer-consumer) β€” with examples and how to fix them?

You said:

yes

ChatGPT said:

πŸ”₯ Excellent β€” now we’re going into the most practical and advanced part of multithreading β€” the problems that happen in real-world systems and how to fix them.

You’ll learn here:
βœ… Deadlock
βœ… Race Condition
βœ… Starvation
βœ… Producer–Consumer problem
βœ… Solutions and best practices


🧩 1️⃣ Deadlock

🧠 Definition:

Deadlock happens when two or more threads are waiting for each other forever, and none of them can proceed.


βš™οΈ Example:

class Resource {
    synchronized void method1(Resource r2) {
        System.out.println(Thread.currentThread().getName() + " locked Resource 1");
        try { Thread.sleep(100); } catch (Exception e) {}
        System.out.println(Thread.currentThread().getName() + " waiting for Resource 2");
        synchronized (r2) {
            System.out.println(Thread.currentThread().getName() + " got Resource 2");
        }
    }

    synchronized void method2(Resource r1) {
        System.out.println(Thread.currentThread().getName() + " locked Resource 2");
        try { Thread.sleep(100); } catch (Exception e) {}
        System.out.println(Thread.currentThread().getName() + " waiting for Resource 1");
        synchronized (r1) {
            System.out.println(Thread.currentThread().getName() + " got Resource 1");
        }
    }
}

public class DeadlockDemo {
    public static void main(String[] args) {
        Resource r1 = new Resource();
        Resource r2 = new Resource();

        Thread t1 = new Thread(() -> r1.method1(r2), "Thread-1");
        Thread t2 = new Thread(() -> r2.method2(r1), "Thread-2");

        t1.start();
        t2.start();
    }
}

🧨 Output:

Thread-1 locked Resource 1
Thread-2 locked Resource 2
Thread-1 waiting for Resource 2
Thread-2 waiting for Resource 1

Both threads are waiting β†’ Deadlock!


βœ… How to Fix Deadlock:

  1. Always acquire locks in the same order

     synchronized (r1) {
         synchronized (r2) {
             // do work
         }
     }
    

    β†’ Both threads lock in same order.

  2. Use tryLock() with timeout (from ReentrantLock)

     if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) {
         if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) {
             // work
         }
     }
    
  3. Minimize nested locking


🧩 2️⃣ Race Condition

🧠 Definition:

When two or more threads modify shared data at the same time, and the output becomes unpredictable.


βš™οΈ Example:

class Counter {
    int count = 0;
    void increment() {
        count++; // not atomic
    }
}

public class RaceConditionDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) counter.increment();
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final Count: " + counter.count);
    }
}

πŸ’₯ Output (unpredictable):

Final Count: 1894   // or 1990 or 2000 β€” not consistent!

βœ… How to Fix Race Condition:

  1. Synchronize access:

     synchronized void increment() {
         count++;
     }
    
  2. Use AtomicInteger:

     AtomicInteger count = new AtomicInteger(0);
     count.incrementAndGet();
    

🧩 3️⃣ Starvation

🧠 Definition:

When one or more threads never get CPU time or access to resources because other threads keep occupying them.

🧍 Example:
A low-priority thread may never execute if high-priority threads keep running.


βš™οΈ Example:

class StarvationDemo {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " executing...");
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        Thread t3 = new Thread(task);

        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.NORM_PRIORITY);
        t3.setPriority(Thread.MIN_PRIORITY);

        t1.start();
        t2.start();
        t3.start();
    }
}

⚠️ Low-priority thread (t3) might not execute often.


βœ… How to Fix Starvation:

  • Avoid overusing thread priorities.

  • Use fair locks (new ReentrantLock(true)).

  • Use ExecutorService for balanced thread scheduling.


🧩 4️⃣ Producer–Consumer Problem

This is a classic synchronization problem β€”
One thread produces data, another consumes it.


βš™οΈ Example Without Synchronization (Problem)

class Shared {
    int value;
    boolean flag = false;
}

public class BadProducerConsumer {
    public static void main(String[] args) {
        Shared s = new Shared();

        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                s.value = i;
                System.out.println("Produced: " + i);
            }
        });

        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Consumed: " + s.value);
            }
        });

        producer.start();
        consumer.start();
    }
}

πŸ’₯ Output:

Consumed: 0
Produced: 1
Produced: 2
Consumed: 2
...

πŸ‘‰ Consumer may read wrong or old data.


βœ… Corrected Example (Using wait() and notify())

class Shared {
    private int data;
    private boolean available = false;

    synchronized void produce(int value) {
        while (available) {
            try { wait(); } catch (InterruptedException e) {}
        }
        data = value;
        available = true;
        System.out.println("Produced: " + value);
        notify();
    }

    synchronized void consume() {
        while (!available) {
            try { wait(); } catch (InterruptedException e) {}
        }
        System.out.println("Consumed: " + data);
        available = false;
        notify();
    }
}

public class ProducerConsumer {
    public static void main(String[] args) {
        Shared shared = new Shared();

        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) shared.produce(i);
        });

        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) shared.consume();
        });

        producer.start();
        consumer.start();
    }
}

βœ… Output:

Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
...

πŸ’‘ Explanation

MethodRole
wait()Makes thread wait (releases lock temporarily)
notify()Wakes up one waiting thread
notifyAll()Wakes up all waiting threads
synchronizedEnsures exclusive access to shared resource

🧩 5️⃣ Other Common Multithreading Problems

ProblemDescriptionFix
LivelockThreads keep changing state to avoid deadlock but never progressAdd delays or coordination logic
Thread InterferenceMultiple threads modify same variableSynchronize or use atomic variables
Context Switching OverheadToo many threads cause CPU overheadUse thread pool or limit thread count
Memory Consistency ErrorsOne thread sees stale valueUse volatile or synchronization

🧩 6️⃣ Thread Communication Summary

MethodBelongs ToDescription
wait()Object classReleases lock and waits
notify()Object classWakes one waiting thread
notifyAll()Object classWakes all waiting threads
sleep()Thread classPauses execution but holds lock
join()Thread classWaits for a thread to finish

🧩 7️⃣ Best Practices in Multithreading

βœ… Always use ExecutorService instead of raw threads
βœ… Avoid shared mutable data
βœ… Use Atomic variables or Locks
βœ… Handle InterruptedException properly
βœ… Use volatile for visibility when needed
βœ… Keep synchronized blocks short
βœ… Use thread-safe collections (ConcurrentHashMap, CopyOnWriteArrayList)
βœ… Always shutdown executors after use


🧩 8️⃣ Real-World Example β€” Selenium Test Execution

import java.util.concurrent.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

class BrowserWorker implements Callable<String> {
    private String url;
    BrowserWorker(String url) { this.url = url; }

    public String call() {
        WebDriver driver = new ChromeDriver();
        driver.get(url);
        String title = driver.getTitle();
        driver.quit();
        return title;
    }
}

public class ParallelTest {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(3);

        Future<String> google = pool.submit(new BrowserWorker("https://www.google.com"));
        Future<String> bing = pool.submit(new BrowserWorker("https://www.bing.com"));

        System.out.println("Google title: " + google.get());
        System.out.println("Bing title: " + bing.get());

        pool.shutdown();
    }
}

βœ… Simulates how TestNG runs tests in parallel safely.


🧩 9️⃣ Interview Highlights

QuestionKey Answer
What causes deadlock?Circular lock dependency
How to prevent deadlock?Acquire locks in same order or use tryLock()
What is a race condition?Multiple threads changing shared data simultaneously
How to avoid race condition?Synchronization or atomic variables
What is starvation?Low-priority thread never gets CPU
What is producer-consumer problem?Coordination between threads sharing a buffer
Difference between wait() and sleep()?wait() releases lock; sleep() does not
How to wake waiting threads?Use notify() or notifyAll()

Would you like me to continue next with a visual guide and deep explanation of Thread Synchronization techniques β€” including synchronized blocks, static sync, object locks, class locks, and how the JVM actually manages monitor locks?

You said:

yes

ChatGPT said:

Perfect πŸ’ͺ β€” now we’ll dive deep into Thread Synchronization, where you’ll learn how threads safely share data and how the JVM actually handles locks (monitors).

We’ll go step-by-step β€” from basic synchronized methods to class-level locks and advanced concepts like volatile, ReentrantLock, and the JVM’s monitor mechanism.


🧩 1️⃣ What is Synchronization?

🧠 Definition:
Synchronization is the process of controlling access to shared resources by multiple threads.

Without it, you’ll face problems like race conditions, inconsistent data, or corrupted state.


πŸ’‘ Example Problem (Without Synchronization)

class Counter {
    int count = 0;
    void increment() {
        count++; // not thread-safe
    }
}

public class SyncDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter c = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) c.increment();
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) c.increment();
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final Count = " + c.count);
    }
}

πŸ’₯ Output (unpredictable):
Final Count = 1974 or 1998 or anything below 2000.

Because both threads modify count simultaneously β†’ Race Condition.


🧩 2️⃣ The synchronized Keyword

It ensures only one thread executes a block/method at a time for a given object.


βœ… Example 1: Synchronized Method

class Counter {
    int count = 0;

    synchronized void increment() {
        count++;
    }
}

Now, only one thread can call increment() on the same object at a time.
So result is always consistent.


βœ… Example 2: Synchronized Block

You can synchronize only a specific portion of code instead of an entire method.

class Counter {
    int count = 0;
    void increment() {
        synchronized(this) {  // 'this' means lock on current object
            count++;
        }
    }
}

πŸ’‘ Why better: It improves performance by limiting lock scope.


🧩 3️⃣ Object Lock vs Class Lock

TypeLock OwnerUsed InExample
Object LockEach instance has its own lockInstance methods or blockssynchronized(this)
Class LockShared among all instancesStatic methods or blockssynchronized(ClassName.class)

βš™οΈ Example

class Demo {
    synchronized void objectLock() {
        System.out.println(Thread.currentThread().getName() + " using Object Lock");
    }

    static synchronized void classLock() {
        System.out.println(Thread.currentThread().getName() + " using Class Lock");
    }
}

public class LockExample {
    public static void main(String[] args) {
        Demo d1 = new Demo();
        Demo d2 = new Demo();

        // Object lock (different locks)
        new Thread(() -> d1.objectLock()).start();
        new Thread(() -> d2.objectLock()).start();

        // Class lock (shared lock)
        new Thread(() -> Demo.classLock()).start();
        new Thread(() -> Demo.classLock()).start();
    }
}

🧩 Result:

  • objectLock() β†’ Both threads execute simultaneously (different objects).

  • classLock() β†’ Only one thread executes at a time (shared class-level lock).


🧩 4️⃣ How the JVM Handles Synchronization (Monitor Concept)

Every Java object has an internal monitor lock managed by JVM.

When a thread enters a synchronized block/method:

  1. It tries to acquire the object’s monitor lock.

  2. If lock is free, thread enters.

  3. If lock is taken, thread goes into BLOCKED state.

  4. When the thread exits synchronized code β†’ it releases the lock.


πŸ”„ Monitor Lock Flow

Thread A enters synchronized block
 ↓
Acquires object’s monitor
 ↓
Executes code
 ↓
Releases monitor
 ↓
Next thread acquires it

🧠 In JVM, this is implemented through MonitorEnter and MonitorExit bytecode instructions.


🧩 5️⃣ synchronized on Different Objects

You can synchronize on:

  • this β†’ instance lock

  • SomeClass.class β†’ class-level lock

  • anyObject β†’ custom lock object

Example:

Object lock1 = new Object();
Object lock2 = new Object();

synchronized (lock1) {
    // only threads with lock1 can enter here
}

synchronized (lock2) {
    // different lock region
}

πŸ‘‰ Use different locks to avoid deadlock and improve performance.


🧩 6️⃣ Static Synchronization

Used when shared resource belongs to the class (not object).

class Bank {
    static int balance = 1000;

    static synchronized void deposit(int amount) {
        balance += amount;
    }
}

➑️ Lock is applied on the Class object, not on any instance.


🧩 7️⃣ volatile Keyword

🧠 Purpose:

volatile ensures visibility of changes across threads.

Without it, one thread might see old (cached) values.

βš™οΈ Example:

class Shared {
    volatile boolean running = true;

    void runTask() {
        while (running) {
            // do work
        }
    }
}

public class VolatileExample {
    public static void main(String[] args) throws Exception {
        Shared s = new Shared();
        Thread t = new Thread(s::runTask);
        t.start();

        Thread.sleep(1000);
        s.running = false;  // visible immediately to thread
        System.out.println("Stopped");
    }
}

Without volatile, the running flag might not be updated in thread’s local cache.


🧩 8️⃣ Advanced: ReentrantLock (from java.util.concurrent.locks)

Provides explicit locking, more flexible than synchronized.

βš™οΈ Example:

import java.util.concurrent.locks.ReentrantLock;

class Printer {
    private final ReentrantLock lock = new ReentrantLock();

    void print(String msg) {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " printing " + msg);
        } finally {
            lock.unlock();
        }
    }
}

public class LockDemo {
    public static void main(String[] args) {
        Printer p = new Printer();
        Runnable task = () -> p.print("Document");

        new Thread(task).start();
        new Thread(task).start();
    }
}

βœ… Features:

  • tryLock() β€” try without blocking

  • lockInterruptibly() β€” can respond to interrupts

  • isLocked() β€” check lock state

  • new ReentrantLock(true) β€” fair lock


🧩 9️⃣ Difference: synchronized vs ReentrantLock

FeaturesynchronizedReentrantLock
Lock typeImplicit (JVM-managed)Explicit (manual)
Try for lock❌ Not possibleβœ… tryLock()
Interrupt lock❌ Noβœ… Yes
Fairness❌ Noβœ… Optional
Auto-releaseβœ… Yes❌ Must unlock manually
Condition waitingLimited (wait()/notify())Advanced (Condition.await())

🧩 πŸ”Ÿ Thread-safe Collections

To avoid manual locking, Java provides thread-safe data structures in java.util.concurrent:

TypeThread-safe Version
MapConcurrentHashMap
ListCopyOnWriteArrayList
SetCopyOnWriteArraySet
QueueBlockingQueue, LinkedBlockingQueue
DequeConcurrentLinkedDeque

🧩 11️⃣ Best Practices for Synchronization

βœ… Keep synchronized blocks as small as possible.
βœ… Avoid nested locks (deadlocks).
βœ… Prefer concurrent collections over manual sync.
βœ… Use volatile for simple visibility problems.
βœ… Always unlock() in finally when using ReentrantLock.
βœ… Don’t block threads inside synchronized methods (e.g., sleep(), wait() unnecessarily).
βœ… Prefer ExecutorService over raw threads.


🧩 12️⃣ Quick Interview Recap

QuestionKey Point
What is synchronization?Mechanism to control access to shared resources
What is intrinsic lock?Built-in monitor lock of an object
Difference between synchronized method & block?Block can target specific section; more efficient
What is class-level lock?Lock acquired on ClassName.class
What is volatile?Ensures variable visibility across threads
Difference between synchronized and ReentrantLock?ReentrantLock gives more control (tryLock, fairness, etc.)
Can constructor be synchronized?❌ No, it’s not needed (object not shared yet)
What are thread-safe collections?Collections from java.util.concurrent package

Would you like me to continue next with a complete tutorial on java.util.concurrent package β€” including ThreadPoolExecutor, Callable & Future, Semaphore, CountDownLatch, and CyclicBarrier β€” all with diagrams and code examples?

You said:

yes

ChatGPT said:

Perfect πŸš€ β€” now we’ll move to the advanced part of multithreading in Java β€” the java.util.concurrent package.
This is where real-world multithreading happens: thread pools, futures, locks, synchronization tools, etc.

Let’s go step-by-step πŸ”½


🧩 1️⃣ Overview: java.util.concurrent Package

This package provides:
βœ… Thread pool management
βœ… Advanced synchronization tools
βœ… Concurrent (thread-safe) collections
βœ… Atomic variables
βœ… Locks and semaphores


πŸ“¦ Main Components:

CategoryClasses / InterfacesPurpose
ExecutorsExecutor, ExecutorService, ThreadPoolExecutor, ScheduledExecutorServiceManage threads efficiently
SynchronizationCountDownLatch, CyclicBarrier, Semaphore, ExchangerCoordinate threads
LocksReentrantLock, ReadWriteLockMore control than synchronized
AtomicAtomicInteger, AtomicBoolean, AtomicReferenceThread-safe operations on single variables
CollectionsConcurrentHashMap, CopyOnWriteArrayListThread-safe collections
Futures & CallablesFuture, CallableReturn results from threads

🧩 2️⃣ Thread Pools (Executor Framework)

Creating threads manually wastes resources β€” thread pools reuse existing threads for efficiency.

βœ… Basic Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3);

        for (int i = 1; i <= 5; i++) {
            int task = i;
            service.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " executing task " + task);
            });
        }

        service.shutdown();
    }
}

🧩 Output:

pool-1-thread-1 executing task 1
pool-1-thread-2 executing task 2
pool-1-thread-3 executing task 3
pool-1-thread-1 executing task 4
pool-1-thread-2 executing task 5

βœ… Threads are reused β†’ faster execution.


βš™οΈ Types of Thread Pools (from Executors class)

MethodDescription
newFixedThreadPool(n)Fixed number of threads
newCachedThreadPool()Grows/shrinks dynamically
newSingleThreadExecutor()Executes tasks sequentially
newScheduledThreadPool(n)Executes tasks with delay or periodically

🧩 3️⃣ Callable and Future (Return Values from Threads)

Unlike Runnable, Callable can return a value and throw exceptions.

βœ… Example:

import java.util.concurrent.*;

public class CallableFutureExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Callable<Integer> task = () -> {
            System.out.println("Calculating...");
            Thread.sleep(1000);
            return 42;
        };

        Future<Integer> future = executor.submit(task);
        System.out.println("Result: " + future.get()); // waits for completion

        executor.shutdown();
    }
}

🧩 Output:

Calculating...
Result: 42

🧠 Key Methods of Future

MethodDescription
get()Waits and returns result
get(timeout, unit)Waits up to given time
isDone()Returns true if task finished
cancel(true)Cancels the task

🧩 4️⃣ CountDownLatch (Wait for Threads to Finish)

Lets one or more threads wait until other threads complete.

βœ… Example:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);

        for (int i = 1; i <= 3; i++) {
            int workerId = i;
            new Thread(() -> {
                System.out.println("Worker " + workerId + " done");
                latch.countDown();
            }).start();
        }

        latch.await(); // waits until countDown() called 3 times
        System.out.println("All workers finished!");
    }
}

🧩 Output:

Worker 1 done
Worker 2 done
Worker 3 done
All workers finished!

🧩 5️⃣ CyclicBarrier (Wait for Each Other)

Used when multiple threads must wait for each other to reach a common point before proceeding.

βœ… Example:

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, 
            () -> System.out.println("All threads reached the barrier!"));

        for (int i = 1; i <= 3; i++) {
            int id = i;
            new Thread(() -> {
                System.out.println("Thread " + id + " reached barrier");
                try {
                    barrier.await(); // waits for others
                } catch (Exception e) {}
                System.out.println("Thread " + id + " continues");
            }).start();
        }
    }
}

🧩 Output:

Thread 1 reached barrier
Thread 2 reached barrier
Thread 3 reached barrier
All threads reached the barrier!
Thread 3 continues
Thread 2 continues
Thread 1 continues

🧩 6️⃣ Semaphore (Limit Thread Access)

Used to control number of threads accessing a resource simultaneously.

βœ… Example:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore sem = new Semaphore(2); // only 2 threads can run at a time

        for (int i = 1; i <= 5; i++) {
            int worker = i;
            new Thread(() -> {
                try {
                    sem.acquire();
                    System.out.println("Worker " + worker + " acquired permit");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                } finally {
                    sem.release();
                    System.out.println("Worker " + worker + " released permit");
                }
            }).start();
        }
    }
}

🧩 Output:

Worker 1 acquired permit
Worker 2 acquired permit
Worker 1 released permit
Worker 3 acquired permit
Worker 2 released permit
...

🧩 7️⃣ Exchanger (Swap Data Between Two Threads)

βœ… Example:

import java.util.concurrent.Exchanger;

public class ExchangerExample {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();

        new Thread(() -> {
            try {
                String data = "Data from Thread A";
                String reply = exchanger.exchange(data);
                System.out.println("Thread A got: " + reply);
            } catch (InterruptedException e) {}
        }).start();

        new Thread(() -> {
            try {
                String data = "Data from Thread B";
                String reply = exchanger.exchange(data);
                System.out.println("Thread B got: " + reply);
            } catch (InterruptedException e) {}
        }).start();
    }
}

🧩 Output:

Thread A got: Data from Thread B
Thread B got: Data from Thread A

🧩 8️⃣ Concurrent Collections

Collection TypeThread-safe VersionKey Feature
MapConcurrentHashMapHigh concurrency
ListCopyOnWriteArrayListSafe for iteration
SetCopyOnWriteArraySetCopy on modification
QueueConcurrentLinkedQueueNon-blocking queue
BlockingQueueLinkedBlockingQueue, ArrayBlockingQueueBlocking producer/consumer

🧩 9️⃣ Atomic Variables

Classes like AtomicInteger, AtomicBoolean allow atomic (thread-safe) operations without locks.

βœ… Example:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.incrementAndGet();
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) counter.incrementAndGet();
        });

        t1.start(); t2.start();
        try { t1.join(); t2.join(); } catch (InterruptedException e) {}

        System.out.println("Final Count = " + counter.get());
    }
}

βœ… Output always:
Final Count = 2000


🧩 πŸ”Ÿ Summary Table (Interview Quick Notes)

ConceptClass/InterfaceUse
Thread PoolExecutorService, ExecutorsManage threads efficiently
CallableCallable<V>Return result from thread
FutureFuture<V>Handle result of async computation
LatchCountDownLatchWait for threads to finish
BarrierCyclicBarrierSynchronize threads at a point
SemaphoreSemaphoreLimit thread access
ExchangerExchanger<T>Exchange data between threads
LockReentrantLockExplicit locking control
AtomicAtomicInteger etc.Lock-free thread-safe counters
Concurrent CollectionsConcurrentHashMap, CopyOnWriteArrayListThread-safe data structures

🧩 11️⃣ Real-Life Example: ThreadPool + Future

import java.util.concurrent.*;

public class WebServerSimulator {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(3);

        Callable<String> task = () -> {
            Thread.sleep(1000);
            return Thread.currentThread().getName() + " processed request";
        };

        for (int i = 1; i <= 5; i++) {
            Future<String> f = pool.submit(task);
            System.out.println(f.get());
        }

        pool.shutdown();
    }
}

🧩 Output:

pool-1-thread-1 processed request
pool-1-thread-2 processed request
pool-1-thread-3 processed request
pool-1-thread-1 processed request
pool-1-thread-2 processed request

βœ… Next step suggestion:
We can now go for a complete β€œProducer–Consumer” project using:

  • BlockingQueue

  • ExecutorService

  • CountDownLatch

More from this blog

SS-JAVA

23 posts