JAVA-00-Multithreading
π₯ 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
| Term | Description |
| Process | An independent program in execution (has its own memory). |
| Thread | A lightweight sub-process inside a process β shares memory and resources with other threads. |
| Multitasking | Running multiple processes at once (e.g., browser + IDE + music). |
| Multithreading | Running 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
| Method | Description |
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.
| Constant | Value | Description |
Thread.MIN_PRIORITY | 1 | Lowest |
Thread.NORM_PRIORITY | 5 | Default |
Thread.MAX_PRIORITY | 10 | Highest |
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
| Type | Keyword/Concept | Used For |
| Method synchronization | synchronized keyword on method | Locks whole method |
| Block synchronization | synchronized(obj) | Locks only block |
| Static synchronization | synchronized static | Locks class level |
| Inter-thread communication | wait(), 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)
| Package | Feature | Description |
java.util.concurrent | Executors | Thread pools |
Future, Callable | Return results from threads | |
CountDownLatch, CyclicBarrier | Thread coordination | |
Lock | Advanced 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
| Concept | Description | Keyword/Class |
| Create Thread | Extend Thread or implement Runnable | Thread, Runnable |
| Run Thread | Start new thread | start() |
| Pause | Sleep thread | sleep() |
| Wait for another | Wait for thread to finish | join() |
| Sync access | Prevent data conflict | synchronized |
| Background task | Low-priority daemon thread | setDaemon(true) |
| Thread pool | Manage reusable threads | ExecutorService |
| Return result | Callable with Future | Future<T> |
| Safe shared variable | Atomic variable | AtomicInteger |
π§© 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
| Method | Description |
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
| Feature | synchronized | Lock (java.util.concurrent.locks) |
| Simplicity | Easier to use | More flexible |
| Reentrant | Yes | Yes |
| Try to acquire | Not possible | Possible (tryLock()) |
| Timeout | Not possible | Possible |
| Fairness | Not possible | Possible (ReentrantLock(true)) |
| Performance | Moderate | Better 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.
| Class | Description |
AtomicInteger | Integer operations |
AtomicBoolean | Boolean |
AtomicLong | Long |
AtomicReference | Reference 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
| Question | Answer |
| 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
| Concept | Key Class | Description |
| Basic Thread | Thread, Runnable | Create & run threads |
| Return value | Callable, Future | Thread result handling |
| Thread pool | ExecutorService | Manage multiple threads |
| Scheduling | ScheduledExecutorService | Timed or repeated tasks |
| Synchronization | synchronized, Lock | Thread safety |
| Coordination | CountDownLatch, CyclicBarrier | Thread sync |
| Control access | Semaphore | Limit concurrent threads |
| Atomic ops | AtomicInteger, 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:
Always acquire locks in the same order
synchronized (r1) { synchronized (r2) { // do work } }β Both threads lock in same order.
Use
tryLock()with timeout (fromReentrantLock)if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) { if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) { // work } }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:
Synchronize access:
synchronized void increment() { count++; }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
| Method | Role |
wait() | Makes thread wait (releases lock temporarily) |
notify() | Wakes up one waiting thread |
notifyAll() | Wakes up all waiting threads |
synchronized | Ensures exclusive access to shared resource |
π§© 5οΈβ£ Other Common Multithreading Problems
| Problem | Description | Fix |
| Livelock | Threads keep changing state to avoid deadlock but never progress | Add delays or coordination logic |
| Thread Interference | Multiple threads modify same variable | Synchronize or use atomic variables |
| Context Switching Overhead | Too many threads cause CPU overhead | Use thread pool or limit thread count |
| Memory Consistency Errors | One thread sees stale value | Use volatile or synchronization |
π§© 6οΈβ£ Thread Communication Summary
| Method | Belongs To | Description |
wait() | Object class | Releases lock and waits |
notify() | Object class | Wakes one waiting thread |
notifyAll() | Object class | Wakes all waiting threads |
sleep() | Thread class | Pauses execution but holds lock |
join() | Thread class | Waits 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
| Question | Key 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
| Type | Lock Owner | Used In | Example |
| Object Lock | Each instance has its own lock | Instance methods or blocks | synchronized(this) |
| Class Lock | Shared among all instances | Static methods or blocks | synchronized(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:
It tries to acquire the objectβs monitor lock.
If lock is free, thread enters.
If lock is taken, thread goes into BLOCKED state.
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 lockSomeClass.classβ class-level lockanyObjectβ 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 blockinglockInterruptibly()β can respond to interruptsisLocked()β check lock statenew ReentrantLock(true)β fair lock
π§© 9οΈβ£ Difference: synchronized vs ReentrantLock
| Feature | synchronized | ReentrantLock |
| Lock type | Implicit (JVM-managed) | Explicit (manual) |
| Try for lock | β Not possible | β
tryLock() |
| Interrupt lock | β No | β Yes |
| Fairness | β No | β Optional |
| Auto-release | β Yes | β Must unlock manually |
| Condition waiting | Limited (wait()/notify()) | Advanced (Condition.await()) |
π§© π Thread-safe Collections
To avoid manual locking, Java provides thread-safe data structures in java.util.concurrent:
| Type | Thread-safe Version |
| Map | ConcurrentHashMap |
| List | CopyOnWriteArrayList |
| Set | CopyOnWriteArraySet |
| Queue | BlockingQueue, LinkedBlockingQueue |
| Deque | ConcurrentLinkedDeque |
π§© 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
| Question | Key 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:
| Category | Classes / Interfaces | Purpose |
| Executors | Executor, ExecutorService, ThreadPoolExecutor, ScheduledExecutorService | Manage threads efficiently |
| Synchronization | CountDownLatch, CyclicBarrier, Semaphore, Exchanger | Coordinate threads |
| Locks | ReentrantLock, ReadWriteLock | More control than synchronized |
| Atomic | AtomicInteger, AtomicBoolean, AtomicReference | Thread-safe operations on single variables |
| Collections | ConcurrentHashMap, CopyOnWriteArrayList | Thread-safe collections |
| Futures & Callables | Future, Callable | Return 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)
| Method | Description |
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
| Method | Description |
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 Type | Thread-safe Version | Key Feature |
Map | ConcurrentHashMap | High concurrency |
List | CopyOnWriteArrayList | Safe for iteration |
Set | CopyOnWriteArraySet | Copy on modification |
Queue | ConcurrentLinkedQueue | Non-blocking queue |
BlockingQueue | LinkedBlockingQueue, ArrayBlockingQueue | Blocking 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)
| Concept | Class/Interface | Use |
| Thread Pool | ExecutorService, Executors | Manage threads efficiently |
| Callable | Callable<V> | Return result from thread |
| Future | Future<V> | Handle result of async computation |
| Latch | CountDownLatch | Wait for threads to finish |
| Barrier | CyclicBarrier | Synchronize threads at a point |
| Semaphore | Semaphore | Limit thread access |
| Exchanger | Exchanger<T> | Exchange data between threads |
| Lock | ReentrantLock | Explicit locking control |
| Atomic | AtomicInteger etc. | Lock-free thread-safe counters |
| Concurrent Collections | ConcurrentHashMap, CopyOnWriteArrayList | Thread-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:
BlockingQueueExecutorServiceCountDownLatch