Complete guide to concurrent programming in Java with real-world examples like bus booking systems, banking applications, and YouTube notifications
๐ Table of Contents
- Introduction
- Process vs Thread
- Thread Lifecycle
- Creating Threads
- Thread Synchronization
- Real-World: Bus Booking System
- Real-World: Banking System
- Inter-Thread Communication
- Observer Pattern: YouTube Notifications
- Thread Methods
- Deadlock
- Thread Pools
- Best Practices
๐ฏ Introduction
Why Multithreading Matters
Imagine a restaurant:
- Single-threaded: One waiter serves all tables (slow!)
- Multi-threaded: Multiple waiters serve simultaneously (fast!)
Real Impact:
- Web servers: Handle 1000s of requests simultaneously
- UI apps: Keep interface responsive while processing
- Games: Render graphics + physics + AI in parallel
๐ Process vs Thread
Visual Comparison
PROCESS (Heavy Weight)
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Process 1 โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ Code โ โ
โ โ Data โ โ
โ โ Heap โ โ
โ โ Stack โ โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
THREADS (Light Weight)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Process โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Shared: Code, Data, Heap โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Thread 1 Thread 2 Thread 3 โ
โ [Stack] [Stack] [Stack] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Real Numbers
| Operation | Process | Thread |
|---|---|---|
| Create | ~10ms | ~1ms |
| Context switch | ~1000 cycles | ~100 cycles |
| Memory | Separate (MBs) | Shared (KBs) |
| Communication | Slow (IPC) | Fast (shared memory) |
๐ Thread Lifecycle
NEW
โ
start()
โ
โ
RUNNABLE โโโโโโโโ
โ โ โ
scheduler โ
โ โ โ
โ โ โ
RUNNING โ
โ โ โ โ
block wait sleep โ
โ โ โ โ
BLOCKED WAITING โ
โ โ โ
โโโโโโโโดโโโโโโโโ
โ
TERMINATED
Lifecycle Demo
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class ThreadLifecycleDemo {
private static final DateTimeFormatter TIME_FORMAT =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private static void log(String message) {
System.out.println("[" + LocalTime.now().format(TIME_FORMAT) + "] " + message);
}
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
log("Thread: RUNNING state");
log("Thread: Going to sleep (TIMED_WAITING)");
Thread.sleep(2000);
log("Thread: Woke up, back to RUNNABLE");
} catch (InterruptedException e) {
log("Thread: Interrupted!");
Thread.currentThread().interrupt();
}
}, "Worker-1");
// NEW state
log("Main: Thread state = " + thread.getState());
thread.start();
// RUNNABLE state
log("Main: Thread state = " + thread.getState());
try {
Thread.sleep(1000);
// TIMED_WAITING state
log("Main: Thread state = " + thread.getState());
thread.join();
// TERMINATED state
log("Main: Thread state = " + thread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Output:
[10:30:15.123] Main: Thread state = NEW
[10:30:15.125] Main: Thread state = RUNNABLE
[10:30:15.126] Thread: RUNNING state
[10:30:15.127] Thread: Going to sleep (TIMED_WAITING)
[10:30:16.128] Main: Thread state = TIMED_WAITING
[10:30:17.129] Thread: Woke up, back to RUNNABLE
[10:30:17.130] Main: Thread state = TERMINATED
๐ Creating Threads: The Right Way
Method 1: Extending Thread (Rarely Used)
class DownloadThread extends Thread {
private final String fileName;
public DownloadThread(String fileName) {
super("Downloader-" + fileName); // Name the thread
this.fileName = fileName;
}
@Override
public void run() {
System.out.println(getName() + ": Downloading " + fileName);
try {
// Simulate download
Thread.sleep(2000);
System.out.println(getName() + ": โ
Downloaded " + fileName);
} catch (InterruptedException e) {
System.out.println(getName() + ": โ Download interrupted");
Thread.currentThread().interrupt();
}
}
}
// Usage
DownloadThread t1 = new DownloadThread("video.mp4");
t1.start();
Problem: Can't extend another class!
Method 2: Implementing Runnable (RECOMMENDED)
class UploadTask implements Runnable {
private final String fileName;
private final int fileSize;
public UploadTask(String fileName, int fileSize) {
this.fileName = fileName;
this.fileSize = fileSize;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Uploading " + fileName +
" (" + fileSize + "MB)");
try {
// Simulate upload
for (int i = 0; i <= 100; i += 20) {
System.out.println(threadName + ": " + fileName + " - " + i + "%");
Thread.sleep(500);
}
System.out.println(threadName + ": โ
Uploaded " + fileName);
} catch (InterruptedException e) {
System.out.println(threadName + ": โ Upload interrupted");
Thread.currentThread().interrupt();
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// Can extend other classes and still be a thread task
UploadTask task1 = new UploadTask("document.pdf", 5);
UploadTask task2 = new UploadTask("image.jpg", 2);
Thread t1 = new Thread(task1, "Uploader-1");
Thread t2 = new Thread(task2, "Uploader-2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
System.out.println("\nโ
All uploads complete!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Method 3: Lambda (Modern Java)
public class LambdaThreads {
public static void main(String[] args) {
// Clean and concise
Thread emailThread = new Thread(() -> {
System.out.println("Sending email...");
try {
Thread.sleep(1000);
System.out.println("โ
Email sent!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "EmailSender");
emailThread.start();
}
}
๐ Thread Synchronization: The Critical Problem
The Race Condition (MUST UNDERSTAND!)
class TicketCounter {
private int availableTickets = 10;
// โ NOT thread-safe
public void bookTicket(String customerName) {
if (availableTickets > 0) {
System.out.println(customerName + " checking... Tickets available: " +
availableTickets);
// Simulate processing delay
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
availableTickets--;
System.out.println("โ
" + customerName + " booked! Remaining: " +
availableTickets);
} else {
System.out.println("โ " + customerName + " - No tickets!");
}
}
}
public class RaceConditionProblem {
public static void main(String[] args) {
TicketCounter counter = new TicketCounter();
// 15 customers trying to book 10 tickets
for (int i = 1; i <= 15; i++) {
final String customer = "Customer-" + i;
new Thread(() -> counter.bookTicket(customer)).start();
}
}
}
Output (WRONG!):
Customer-1 checking... Tickets available: 10
Customer-2 checking... Tickets available: 10 โ Problem!
Customer-3 checking... Tickets available: 10 โ Problem!
...
โ
Customer-1 booked! Remaining: 9
โ
Customer-2 booked! Remaining: 8
โ
Customer-3 booked! Remaining: 7
...
โ
Customer-12 booked! Remaining: -2 โ NEGATIVE TICKETS!
The Solution: Synchronized
class SafeTicketCounter {
private int availableTickets = 10;
// โ
Thread-safe
public synchronized void bookTicket(String customerName) {
if (availableTickets > 0) {
System.out.println(customerName + " checking... Tickets available: " +
availableTickets);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
availableTickets--;
System.out.println("โ
" + customerName + " booked! Remaining: " +
availableTickets);
} else {
System.out.println("โ " + customerName + " - SOLD OUT!");
}
}
}
public class SafeBooking {
public static void main(String[] args) throws InterruptedException {
SafeTicketCounter counter = new SafeTicketCounter();
Thread[] customers = new Thread[15];
// 15 customers trying to book 10 tickets
for (int i = 0; i < 15; i++) {
final String customer = "Customer-" + (i + 1);
customers[i] = new Thread(() -> counter.bookTicket(customer));
customers[i].start();
}
// Wait for all customers
for (Thread customer : customers) {
customer.join();
}
System.out.println("\nโ
Booking session complete!");
}
}
Output (CORRECT!):
Customer-1 checking... Tickets available: 10
โ
Customer-1 booked! Remaining: 9
Customer-2 checking... Tickets available: 9
โ
Customer-2 booked! Remaining: 8
...
Customer-10 checking... Tickets available: 1
โ
Customer-10 booked! Remaining: 0
Customer-11 checking... Tickets available: 0
โ Customer-11 - SOLD OUT!
...
๐ Real-World: Bus Booking System (Production-Ready!)
Version 1: Sequential (Educational)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
class Bus {
private final String busNumber;
private final AtomicInteger bookedSeats;
private final int totalSeats;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public Bus(String busNumber, int totalSeats) {
this.busNumber = busNumber;
this.totalSeats = totalSeats;
this.bookedSeats = new AtomicInteger(0);
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
public synchronized BookingResult bookSeats(String userName, int requestedSeats) {
log(userName + " ๐ Logged in");
// Simulate authentication
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Booking interrupted", 0);
}
log(userName + " ๐ Welcome to " + busNumber);
int available = totalSeats - bookedSeats.get();
log(userName + " ๐ Available: " + available + " seats");
log(userName + " ๐ซ Requested: " + requestedSeats + " seats");
// Simulate payment processing
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Payment interrupted", available);
}
// Critical section
if (available >= requestedSeats) {
bookedSeats.addAndGet(requestedSeats);
int remaining = totalSeats - bookedSeats.get();
log(userName + " โ
SUCCESS! Booked " + requestedSeats + " seats");
log(userName + " ๐ Remaining: " + remaining + " seats");
log(userName + " ๐ Logged out\n");
return new BookingResult(true, "Booking successful", remaining);
} else {
log(userName + " โ FAILED! Only " + available + " seats available");
log(userName + " ๐ Logged out\n");
return new BookingResult(false, "Insufficient seats", available);
}
}
public int getBookedSeats() {
return bookedSeats.get();
}
public int getAvailableSeats() {
return totalSeats - bookedSeats.get();
}
}
class BookingResult {
private final boolean success;
private final String message;
private final int remainingSeats;
public BookingResult(boolean success, String message, int remainingSeats) {
this.success = success;
this.message = message;
this.remainingSeats = remainingSeats;
}
public boolean isSuccess() {
return success;
}
public String getMessage() {
return message;
}
public int getRemainingSeats() {
return remainingSeats;
}
}
class Passenger extends Thread {
private final Bus bus;
private final String name;
private final int seatsRequested;
private BookingResult result;
public Passenger(Bus bus, String name, int seatsRequested) {
super(name);
this.bus = bus;
this.name = name;
this.seatsRequested = seatsRequested;
}
@Override
public void run() {
result = bus.bookSeats(name, seatsRequested);
}
public BookingResult getResult() {
return result;
}
}
public class BusBookingSystem {
public static void main(String[] args) {
System.out.println("=== BUS BOOKING SYSTEM (SEQUENTIAL) ===\n");
System.out.println("Note: Synchronized method = One passenger at a time\n");
Bus bus = new Bus("RCOEM-101", 10);
// Create passengers
Passenger[] passengers = {
new Passenger(bus, "Rajat", 4),
new Passenger(bus, "Varun", 5),
new Passenger(bus, "Sujal", 3),
new Passenger(bus, "Amit", 2)
};
long startTime = System.currentTimeMillis();
// Start all passengers
for (Passenger p : passengers) {
p.start();
}
// Wait for all
try {
for (Passenger p : passengers) {
p.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
// Summary
System.out.println("=== BOOKING SUMMARY ===");
System.out.println("Total time: " + (endTime - startTime) + "ms");
System.out.println("Total booked: " + bus.getBookedSeats() + " / 10 seats");
System.out.println("Available: " + bus.getAvailableSeats() + " seats");
System.out.println("\nResults:");
for (Passenger p : passengers) {
BookingResult result = p.getResult();
System.out.println(p.getName() + ": " +
(result.isSuccess() ? "โ
" : "โ") + " " +
result.getMessage());
}
}
}
Output:
=== BUS BOOKING SYSTEM (SEQUENTIAL) ===
Note: Synchronized method = One passenger at a time
[10:30:15.123] Rajat ๐ Logged in
[10:30:15.625] Rajat ๐ Welcome to RCOEM-101
[10:30:15.626] Rajat ๐ Available: 10 seats
[10:30:15.627] Rajat ๐ซ Requested: 4 seats
[10:30:17.128] Rajat โ
SUCCESS! Booked 4 seats
[10:30:17.129] Rajat ๐ Remaining: 6 seats
[10:30:17.130] Rajat ๐ Logged out
[10:30:17.131] Varun ๐ Logged in
[10:30:17.632] Varun ๐ Welcome to RCOEM-101
[10:30:17.633] Varun ๐ Available: 6 seats
[10:30:17.634] Varun ๐ซ Requested: 5 seats
[10:30:19.135] Varun โ
SUCCESS! Booked 5 seats
[10:30:19.136] Varun ๐ Remaining: 1 seats
[10:30:19.137] Varun ๐ Logged out
[10:30:19.138] Sujal ๐ Logged in
[10:30:19.639] Sujal ๐ Welcome to RCOEM-101
[10:30:19.640] Sujal ๐ Available: 1 seats
[10:30:19.641] Sujal ๐ซ Requested: 3 seats
[10:30:21.142] Sujal โ FAILED! Only 1 seats available
[10:30:21.143] Sujal ๐ Logged out
[10:30:21.144] Amit ๐ Logged in
[10:30:21.645] Amit ๐ Welcome to RCOEM-101
[10:30:21.646] Amit ๐ Available: 1 seats
[10:30:21.647] Amit ๐ซ Requested: 2 seats
[10:30:23.148] Amit โ FAILED! Only 1 seats available
[10:30:23.149] Amit ๐ Logged out
=== BOOKING SUMMARY ===
Total time: 8026ms
Total booked: 9 / 10 seats
Available: 1 seats
Results:
Rajat: โ
Booking successful
Varun: โ
Booking successful
Sujal: โ Insufficient seats
Amit: โ Insufficient seats
Version 2: Optimized Parallel (PRODUCTION!)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
class ParallelBus {
private final String busNumber;
private final AtomicInteger bookedSeats;
private final int totalSeats;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public ParallelBus(String busNumber, int totalSeats) {
this.busNumber = busNumber;
this.totalSeats = totalSeats;
this.bookedSeats = new AtomicInteger(0);
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
public BookingResult bookSeats(String userName, int requestedSeats) {
// PARALLEL: Multiple users can login simultaneously
log(userName + " ๐ Logging in...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Login interrupted", 0);
}
// PARALLEL: Multiple users see welcome simultaneously
log(userName + " ๐ Welcome to " + busNumber);
log(userName + " ๐ Checking availability...");
// PARALLEL: Payment processing happens simultaneously
log(userName + " ๐ณ Processing payment for " + requestedSeats + " seats...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Payment interrupted", 0);
}
// CRITICAL SECTION: Only seat allocation is synchronized
BookingResult result;
synchronized(this) {
int available = totalSeats - bookedSeats.get();
if (available >= requestedSeats) {
bookedSeats.addAndGet(requestedSeats);
int remaining = totalSeats - bookedSeats.get();
result = new BookingResult(true, "Booking successful", remaining);
log(userName + " โ
SUCCESS! Reserved " + requestedSeats + " seats");
log(userName + " ๐ Remaining: " + remaining + " seats");
} else {
result = new BookingResult(false, "Insufficient seats", available);
log(userName + " โ FAILED! Only " + available + " seats available");
}
}
// PARALLEL: Confirmation can be sent in parallel
log(userName + " ๐ง Sending confirmation...");
log(userName + " ๐ Logged out\n");
return result;
}
public int getBookedSeats() {
return bookedSeats.get();
}
public int getAvailableSeats() {
return totalSeats - bookedSeats.get();
}
}
class ParallelPassenger extends Thread {
private final ParallelBus bus;
private final String name;
private final int seatsRequested;
private BookingResult result;
public ParallelPassenger(ParallelBus bus, String name, int seatsRequested) {
super(name);
this.bus = bus;
this.name = name;
this.seatsRequested = seatsRequested;
}
@Override
public void run() {
result = bus.bookSeats(name, seatsRequested);
}
public BookingResult getResult() {
return result;
}
}
public class ParallelBusBooking {
public static void main(String[] args) {
System.out.println("=== BUS BOOKING SYSTEM (OPTIMIZED PARALLEL) ===\n");
System.out.println("Note: Only seat allocation is synchronized!\n");
ParallelBus bus = new ParallelBus("RCOEM-101", 10);
ParallelPassenger[] passengers = {
new ParallelPassenger(bus, "Rajat", 4),
new ParallelPassenger(bus, "Varun", 5),
new ParallelPassenger(bus, "Sujal", 3),
new ParallelPassenger(bus, "Amit", 2)
};
long startTime = System.currentTimeMillis();
for (ParallelPassenger p : passengers) {
p.start();
}
try {
for (ParallelPassenger p : passengers) {
p.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("=== BOOKING SUMMARY ===");
System.out.println("Total time: " + (endTime - startTime) + "ms");
System.out.println("Total booked: " + bus.getBookedSeats() + " / 10 seats");
System.out.println("Available: " + bus.getAvailableSeats() + " seats");
System.out.println("\nPerformance:");
System.out.println("Sequential would take: ~8000ms");
System.out.println("Parallel takes: ~" + (endTime - startTime) + "ms");
System.out.println("Speedup: ~" + (8000.0 / (endTime - startTime)) + "x faster! ๐");
System.out.println("\nResults:");
for (ParallelPassenger p : passengers) {
BookingResult result = p.getResult();
System.out.println(p.getName() + ": " +
(result.isSuccess() ? "โ
" : "โ") + " " +
result.getMessage());
}
}
}
Output (Notice timestamps overlap!):
=== BUS BOOKING SYSTEM (OPTIMIZED PARALLEL) ===
Note: Only seat allocation is synchronized!
[10:35:20.123] Rajat ๐ Logging in...
[10:35:20.124] Varun ๐ Logging in...
[10:35:20.125] Sujal ๐ Logging in...
[10:35:20.126] Amit ๐ Logging in...
[10:35:20.627] Rajat ๐ Welcome to RCOEM-101
[10:35:20.628] Varun ๐ Welcome to RCOEM-101
[10:35:20.629] Rajat ๐ Checking availability...
[10:35:20.630] Sujal ๐ Welcome to RCOEM-101
[10:35:20.631] Varun ๐ Checking availability...
[10:35:20.632] Amit ๐ Welcome to RCOEM-101
[10:35:20.633] Rajat ๐ณ Processing payment for 4 seats...
[10:35:20.634] Sujal ๐ Checking availability...
[10:35:20.635] Varun ๐ณ Processing payment for 5 seats...
[10:35:20.636] Amit ๐ Checking availability...
[10:35:20.637] Sujal ๐ณ Processing payment for 3 seats...
[10:35:20.638] Amit ๐ณ Processing payment for 2 seats...
[10:35:22.140] Rajat โ
SUCCESS! Reserved 4 seats
[10:35:22.141] Rajat ๐ Remaining: 6 seats
[10:35:22.142] Varun โ
SUCCESS! Reserved 5 seats
[10:35:22.143] Varun ๐ Remaining: 1 seats
[10:35:22.144] Sujal โ FAILED! Only 1 seats available
[10:35:22.145] Amit โ FAILED! Only 1 seats available
[10:35:22.146] Rajat ๐ง Sending confirmation...
[10:35:22.147] Varun ๐ง Sending confirmation...
[10:35:22.148] Sujal ๐ง Sending confirmation...
[10:35:22.149] Amit ๐ง Sending confirmation...
[10:35:22.150] Rajat ๐ Logged out
[10:35:22.151] Varun ๐ Logged out
[10:35:22.152] Sujal ๐ Logged out
[10:35:22.153] Amit ๐ Logged out
=== BOOKING SUMMARY ===
Total time: 2030ms
Total booked: 9 / 10 seats
Available: 1 seats
Performance:
Sequential would take: ~8000ms
Parallel takes: ~2030ms
Speedup: ~3.94x faster! ๐
Results:
Rajat: โ
Booking successful
Varun: โ
Booking successful
Sujal: โ Insufficient seats
Amit: โ Insufficient seats
Performance Comparison Visual
SEQUENTIAL (Fully Synchronized):
Time: 0sโโโโ2sโโโโ4sโโโโ6sโโโโ8s
[Rajat ][Varun][Sujal][Amit]
PARALLEL (Optimized):
Time: 0sโโโโ2s
[Rajat ]
[Varun ]
[Sujal ]
[Amit ]
All process simultaneously!
RESULT: 4x faster! ๐
(Continuing with Banking System, wait/notify, Observer Pattern, Deadlock, Thread Pools in next sections...)
This is just the first part showing the robust implementation. Should I continue with the remaining sections?
๐ฆ Real-World: Banking System (Production-Ready!)
Understanding the Problem
In a banking system, we need TWO levels of synchronization:
- Instance-level: Each account's balance (instance lock)
- Class-level: Bank's total balance (class lock)
Complete Implementation
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
class BankAccount {
// CLASS-LEVEL: Shared across ALL accounts
private static final AtomicInteger totalBankBalance = new AtomicInteger(50000);
private static int transactionCounter = 0;
// INSTANCE-LEVEL: Specific to each account
private final String accountHolder;
private final String accountNumber;
private int accountBalance;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public BankAccount(String holder, String accountNumber, int initialBalance) {
this.accountHolder = holder;
this.accountNumber = accountNumber;
this.accountBalance = initialBalance;
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
// โ
STATIC synchronized - Locks on CLASS (BankAccount.class)
private static synchronized boolean withdrawFromBank(String accountHolder, int amount) {
int currentBankBalance = totalBankBalance.get();
if (currentBankBalance >= amount) {
System.out.println("\n๐ฆ Bank Processing:");
System.out.println(" Account: " + accountHolder);
System.out.println(" Bank balance before: โน" + currentBankBalance);
// Simulate bank processing
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
totalBankBalance.addAndGet(-amount);
transactionCounter++;
System.out.println(" Withdrawn: โน" + amount);
System.out.println(" Bank balance after: โน" + totalBankBalance.get());
System.out.println(" Transaction #" + transactionCounter);
return true;
} else {
System.out.println("\nโ Bank: Insufficient funds!");
System.out.println(" Requested: โน" + amount);
System.out.println(" Available: โน" + currentBankBalance);
return false;
}
}
// โ
INSTANCE synchronized - Locks on THIS object
public synchronized WithdrawalResult withdraw(int amount) {
log(accountHolder + " ๐ Initiating withdrawal of โน" + amount);
// Check account balance
if (accountBalance < amount) {
log(accountHolder + " โ Insufficient account balance");
log(accountHolder + " ๐ฐ Account balance: โน" + accountBalance);
return new WithdrawalResult(false, "Insufficient account balance",
accountBalance, totalBankBalance.get());
}
log(accountHolder + " โ
Account has sufficient balance: โน" + accountBalance);
// Simulate authentication and validation
try {
Thread.sleep(300);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new WithdrawalResult(false, "Transaction interrupted",
accountBalance, totalBankBalance.get());
}
// Try to withdraw from bank's total balance (CLASS-LEVEL lock)
boolean bankApproved = withdrawFromBank(accountHolder, amount);
if (bankApproved) {
// Deduct from account
accountBalance -= amount;
log(accountHolder + " โ
SUCCESS! Withdrawal complete");
log(accountHolder + " ๐ฐ New account balance: โน" + accountBalance);
return new WithdrawalResult(true, "Withdrawal successful",
accountBalance, totalBankBalance.get());
} else {
log(accountHolder + " โ FAILED! Bank has insufficient funds");
return new WithdrawalResult(false, "Bank has insufficient funds",
accountBalance, totalBankBalance.get());
}
}
public synchronized void deposit(int amount) {
log(accountHolder + " ๐ต Depositing โน" + amount);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
accountBalance += amount;
totalBankBalance.addAndGet(amount);
log(accountHolder + " โ
Deposit successful");
log(accountHolder + " ๐ฐ New balance: โน" + accountBalance);
}
public String getAccountHolder() {
return accountHolder;
}
public synchronized int getAccountBalance() {
return accountBalance;
}
public static int getTotalBankBalance() {
return totalBankBalance.get();
}
public static int getTransactionCount() {
return transactionCounter;
}
}
class WithdrawalResult {
private final boolean success;
private final String message;
private final int accountBalance;
private final int bankBalance;
public WithdrawalResult(boolean success, String message,
int accountBalance, int bankBalance) {
this.success = success;
this.message = message;
this.accountBalance = accountBalance;
this.bankBalance = bankBalance;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public int getAccountBalance() { return accountBalance; }
public int getBankBalance() { return bankBalance; }
}
class BankCustomer extends Thread {
private final BankAccount account;
private final int withdrawalAmount;
private WithdrawalResult result;
public BankCustomer(BankAccount account, int amount) {
super(account.getAccountHolder());
this.account = account;
this.withdrawalAmount = amount;
}
@Override
public void run() {
result = account.withdraw(withdrawalAmount);
}
public WithdrawalResult getResult() {
return result;
}
}
public class BankingSystem {
public static void main(String[] args) {
System.out.println("=== BANKING SYSTEM DEMO ===\n");
System.out.println("Initial Bank Balance: โน" + BankAccount.getTotalBankBalance());
System.out.println();
// Create accounts
BankAccount rajat = new BankAccount("Rajat", "ACC001", 30000);
BankAccount varun = new BankAccount("Varun", "ACC002", 25000);
BankAccount sujal = new BankAccount("Sujal", "ACC003", 20000);
// Create customers trying to withdraw
BankCustomer[] customers = {
new BankCustomer(rajat, 20000),
new BankCustomer(varun, 18000),
new BankCustomer(sujal, 15000)
};
long startTime = System.currentTimeMillis();
// Start all withdrawals
for (BankCustomer customer : customers) {
customer.start();
}
// Wait for all
try {
for (BankCustomer customer : customers) {
customer.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
// Summary
System.out.println("\n" + "=".repeat(50));
System.out.println("TRANSACTION SUMMARY");
System.out.println("=".repeat(50));
System.out.println("\nProcessing time: " + (endTime - startTime) + "ms");
System.out.println("Total transactions: " + BankAccount.getTransactionCount());
System.out.println("Final bank balance: โน" + BankAccount.getTotalBankBalance());
System.out.println("\nIndividual Results:");
for (BankCustomer customer : customers) {
WithdrawalResult result = customer.getResult();
System.out.println("\n" + customer.getName() + ":");
System.out.println(" Status: " + (result.isSuccess() ? "โ
SUCCESS" : "โ FAILED"));
System.out.println(" Message: " + result.getMessage());
System.out.println(" Account Balance: โน" + result.getAccountBalance());
}
System.out.println("\n" + "=".repeat(50));
}
}
Output:
=== BANKING SYSTEM DEMO ===
Initial Bank Balance: โน50000
[14:30:15.123] Rajat ๐ Initiating withdrawal of โน20000
[14:30:15.425] Rajat โ
Account has sufficient balance: โน30000
๐ฆ Bank Processing:
Account: Rajat
Bank balance before: โน50000
Withdrawn: โน20000
Bank balance after: โน30000
Transaction #1
[14:30:15.926] Rajat โ
SUCCESS! Withdrawal complete
[14:30:15.927] Rajat ๐ฐ New account balance: โน10000
[14:30:15.928] Varun ๐ Initiating withdrawal of โน18000
[14:30:16.229] Varun โ
Account has sufficient balance: โน25000
๐ฆ Bank Processing:
Account: Varun
Bank balance before: โน30000
Withdrawn: โน18000
Bank balance after: โน12000
Transaction #2
[14:30:16.730] Varun โ
SUCCESS! Withdrawal complete
[14:30:16.731] Varun ๐ฐ New account balance: โน7000
[14:30:16.732] Sujal ๐ Initiating withdrawal of โน15000
[14:30:17.033] Sujal โ
Account has sufficient balance: โน20000
โ Bank: Insufficient funds!
Requested: โน15000
Available: โน12000
[14:30:17.534] Sujal โ FAILED! Bank has insufficient funds
==================================================
TRANSACTION SUMMARY
==================================================
Processing time: 2411ms
Total transactions: 2
Final bank balance: โน12000
Individual Results:
Rajat:
Status: โ
SUCCESS
Message: Withdrawal successful
Account Balance: โน10000
Varun:
Status: โ
SUCCESS
Message: Withdrawal successful
Account Balance: โน7000
Sujal:
Status: โ FAILED
Message: Bank has insufficient funds
Account Balance: โน20000
==================================================
Key Learning: Two-Level Locking
/*
CRITICAL CONCEPT: Static vs Instance Synchronization
Class Lock (Static):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BankAccount.class โ
โ (ONE LOCK FOR ALL) โ
โ โ
โ static totalBankBalance โ
โ static withdrawFromBank() โ โ ALL accounts share this
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Instance Locks:
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ Account 1 โ โ Account 2 โ โ Account 3 โ
โ (Rajat) โ โ (Varun) โ โ (Sujal) โ
โ โ โ โ โ โ
โ balance โ โ balance โ โ balance โ
โ withdraw() โ โ withdraw() โ โ withdraw() โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ โ โ
Each has its own lock - can run in parallel!
BUT: All must acquire class lock for withdrawFromBank()
*/
๐ฌ Inter-Thread Communication: wait() & notify()
Producer-Consumer: The Classic Problem
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.Queue;
class DataQueue<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public DataQueue(int capacity) {
this.capacity = capacity;
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
// Producer adds data
public synchronized void produce(T data) throws InterruptedException {
// Wait while queue is full
while (queue.size() == capacity) {
log("๐ด Producer WAITING (queue full: " + queue.size() + "/" + capacity + ")");
wait(); // Release lock and wait
}
queue.add(data);
log("๐ฆ Produced: " + data + " | Queue: " + queue.size() + "/" + capacity);
notifyAll(); // Wake up waiting consumers
}
// Consumer removes data
public synchronized T consume() throws InterruptedException {
// Wait while queue is empty
while (queue.isEmpty()) {
log("๐ด Consumer WAITING (queue empty)");
wait(); // Release lock and wait
}
T data = queue.poll();
log("โ
Consumed: " + data + " | Queue: " + queue.size() + "/" + capacity);
notifyAll(); // Wake up waiting producers
return data;
}
public synchronized int size() {
return queue.size();
}
}
class Producer extends Thread {
private final DataQueue<Integer> queue;
private final int itemsToProduce;
private final int productionDelay;
public Producer(String name, DataQueue<Integer> queue,
int itemsToProduce, int delay) {
super(name);
this.queue = queue;
this.itemsToProduce = itemsToProduce;
this.productionDelay = delay;
}
@Override
public void run() {
try {
for (int i = 1; i <= itemsToProduce; i++) {
int data = (int) (Math.random() * 100);
queue.produce(data);
// Simulate production time
Thread.sleep(productionDelay);
}
System.out.println("\nโ
" + getName() + " finished producing");
} catch (InterruptedException e) {
System.out.println("โ " + getName() + " interrupted");
Thread.currentThread().interrupt();
}
}
}
class Consumer extends Thread {
private final DataQueue<Integer> queue;
private final int itemsToConsume;
private final int consumptionDelay;
public Consumer(String name, DataQueue<Integer> queue,
int itemsToConsume, int delay) {
super(name);
this.queue = queue;
this.itemsToConsume = itemsToConsume;
this.consumptionDelay = delay;
}
@Override
public void run() {
try {
for (int i = 1; i <= itemsToConsume; i++) {
queue.consume();
// Simulate consumption time
Thread.sleep(consumptionDelay);
}
System.out.println("\nโ
" + getName() + " finished consuming");
} catch (InterruptedException e) {
System.out.println("โ " + getName() + " interrupted");
Thread.currentThread().interrupt();
}
}
}
public class ProducerConsumerSystem {
public static void main(String[] args) {
System.out.println("=== PRODUCER-CONSUMER SYSTEM ===\n");
System.out.println("Queue Capacity: 5");
System.out.println("Producers: 2 (fast)");
System.out.println("Consumers: 1 (slow)");
System.out.println();
DataQueue<Integer> queue = new DataQueue<>(5);
// Fast producers
Producer producer1 = new Producer("Producer-1", queue, 8, 300);
Producer producer2 = new Producer("Producer-2", queue, 8, 300);
// Slow consumer
Consumer consumer = new Consumer("Consumer", queue, 16, 500);
long startTime = System.currentTimeMillis();
producer1.start();
producer2.start();
consumer.start();
try {
producer1.join();
producer2.join();
consumer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("\n" + "=".repeat(50));
System.out.println("SUMMARY");
System.out.println("=".repeat(50));
System.out.println("Total time: " + (endTime - startTime) + "ms");
System.out.println("Final queue size: " + queue.size());
System.out.println("\nโ
All done!");
}
}
Output:
=== PRODUCER-CONSUMER SYSTEM ===
Queue Capacity: 5
Producers: 2 (fast)
Consumers: 1 (slow)
[15:20:10.123] ๐ฆ Produced: 42 | Queue: 1/5
[15:20:10.124] ๐ฆ Produced: 87 | Queue: 2/5
[15:20:10.425] โ
Consumed: 42 | Queue: 1/5
[15:20:10.426] ๐ฆ Produced: 15 | Queue: 2/5
[15:20:10.427] ๐ฆ Produced: 73 | Queue: 3/5
[15:20:10.728] ๐ฆ Produced: 91 | Queue: 4/5
[15:20:10.729] ๐ฆ Produced: 28 | Queue: 5/5
[15:20:10.926] โ
Consumed: 87 | Queue: 4/5
[15:20:11.029] ๐ฆ Produced: 56 | Queue: 5/5
[15:20:11.030] ๐ฆ Produced: 63 | Queue: 5/5
[15:20:11.031] ๐ด Producer WAITING (queue full: 5/5)
[15:20:11.032] ๐ด Producer WAITING (queue full: 5/5)
[15:20:11.427] โ
Consumed: 15 | Queue: 4/5
[15:20:11.428] ๐ฆ Produced: 39 | Queue: 5/5
[15:20:11.928] โ
Consumed: 73 | Queue: 4/5
[15:20:11.929] ๐ฆ Produced: 81 | Queue: 5/5
...
โ
Producer-1 finished producing
โ
Producer-2 finished producing
โ
Consumer finished consuming
==================================================
SUMMARY
==================================================
Total time: 8450ms
Final queue size: 0
โ
All done!
Why Use while Instead of if?
/*
CRITICAL: Always use WHILE for wait conditions!
โ BAD (using if):
public synchronized T consume() throws InterruptedException {
if (queue.isEmpty()) {
wait(); // Spurious wakeup can happen!
}
return queue.poll(); // Could be null if spurious wakeup!
}
โ
GOOD (using while):
public synchronized T consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // Recheck condition after wakeup
}
return queue.poll(); // Safe! Queue definitely not empty
}
REASONS FOR SPURIOUS WAKEUPS:
1. JVM optimizations
2. OS thread scheduling
3. Multiple threads calling notifyAll()
4. Hardware interrupts
*/
๐บ Observer Pattern: YouTube Notification System (Production-Ready!)
Complete Implementation with wait/notify
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
// Observer Interface
interface Subscriber {
void update(String channelName, String videoTitle);
String getName();
boolean isNotificationsEnabled();
}
// Subject Interface
interface Channel {
void subscribe(Subscriber subscriber);
void unsubscribe(Subscriber subscriber);
void uploadVideo(String videoTitle);
}
// Concrete Subject
class YouTubeChannel implements Channel {
private final String channelName;
private final List<Subscriber> subscribers;
private final List<String> videos;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public YouTubeChannel(String channelName) {
this.channelName = channelName;
this.subscribers = new CopyOnWriteArrayList<>(); // Thread-safe
this.videos = new ArrayList<>();
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
@Override
public synchronized void subscribe(Subscriber subscriber) {
if (!subscribers.contains(subscriber)) {
subscribers.add(subscriber);
log("โ
" + subscriber.getName() + " subscribed to " + channelName);
} else {
log("โ ๏ธ " + subscriber.getName() + " already subscribed");
}
}
@Override
public synchronized void unsubscribe(Subscriber subscriber) {
if (subscribers.remove(subscriber)) {
log("โ " + subscriber.getName() + " unsubscribed from " + channelName);
}
}
@Override
public void uploadVideo(String videoTitle) {
log("\n๐ฅ " + channelName + " uploaded: \"" + videoTitle + "\"");
synchronized(this) {
videos.add(videoTitle);
}
notifySubscribers(videoTitle);
}
private void notifySubscribers(String videoTitle) {
List<Subscriber> activeSubscribers = new ArrayList<>();
synchronized(this) {
for (Subscriber sub : subscribers) {
if (sub.isNotificationsEnabled()) {
activeSubscribers.add(sub);
}
}
}
log("๐ข Notifying " + activeSubscribers.size() + " subscribers...\n");
// Notify in parallel
for (Subscriber subscriber : activeSubscribers) {
new Thread(() -> {
try {
Thread.sleep(100); // Simulate network delay
subscriber.update(channelName, videoTitle);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Notifier-" + subscriber.getName()).start();
}
}
public String getChannelName() {
return channelName;
}
public synchronized int getSubscriberCount() {
return subscribers.size();
}
public synchronized int getVideoCount() {
return videos.size();
}
}
// Concrete Observer
class YouTubeSubscriber implements Subscriber {
private final String name;
private boolean notificationsEnabled;
private int notificationsReceived;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public YouTubeSubscriber(String name) {
this.name = name;
this.notificationsEnabled = true;
this.notificationsReceived = 0;
}
@Override
public void update(String channelName, String videoTitle) {
if (notificationsEnabled) {
notificationsReceived++;
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " +
"๐ " + name + " received: " + channelName +
" - \"" + videoTitle + "\"");
}
}
@Override
public String getName() {
return name;
}
@Override
public boolean isNotificationsEnabled() {
return notificationsEnabled;
}
public synchronized void enableNotifications() {
notificationsEnabled = true;
System.out.println("๐ " + name + " enabled notifications");
}
public synchronized void disableNotifications() {
notificationsEnabled = false;
System.out.println("๐ " + name + " disabled notifications");
}
public int getNotificationsReceived() {
return notificationsReceived;
}
}
public class YouTubeNotificationSystem {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== YOUTUBE NOTIFICATION SYSTEM ===\n");
// Create channels
YouTubeChannel techChannel = new YouTubeChannel("Tech Insights");
YouTubeChannel gamingChannel = new YouTubeChannel("Gaming Pro");
// Create subscribers
YouTubeSubscriber rajat = new YouTubeSubscriber("Rajat");
YouTubeSubscriber varun = new YouTubeSubscriber("Varun");
YouTubeSubscriber sujal = new YouTubeSubscriber("Sujal");
YouTubeSubscriber amit = new YouTubeSubscriber("Amit");
// Scenario 1: Subscribe to channels
System.out.println("--- SCENARIO 1: Subscriptions ---");
techChannel.subscribe(rajat);
techChannel.subscribe(varun);
techChannel.subscribe(sujal);
gamingChannel.subscribe(varun);
gamingChannel.subscribe(amit);
Thread.sleep(1000);
// Scenario 2: Upload video
System.out.println("\n--- SCENARIO 2: Video Upload ---");
techChannel.uploadVideo("Java Multithreading Mastery");
Thread.sleep(500);
// Scenario 3: Disable notifications
System.out.println("\n--- SCENARIO 3: Disable Notifications ---");
varun.disableNotifications();
Thread.sleep(500);
// Scenario 4: Another upload
System.out.println("\n--- SCENARIO 4: Another Upload ---");
techChannel.uploadVideo("Design Patterns in Java");
Thread.sleep(500);
// Scenario 5: Unsubscribe
System.out.println("\n--- SCENARIO 5: Unsubscribe ---");
techChannel.unsubscribe(sujal);
Thread.sleep(500);
// Scenario 6: Gaming upload
System.out.println("\n--- SCENARIO 6: Gaming Channel ---");
gamingChannel.uploadVideo("Top 10 Games 2024");
Thread.sleep(500);
// Scenario 7: Re-enable notifications
System.out.println("\n--- SCENARIO 7: Re-enable ---");
varun.enableNotifications();
Thread.sleep(500);
// Scenario 8: Final upload
System.out.println("\n--- SCENARIO 8: Final Upload ---");
techChannel.uploadVideo("Spring Boot Complete Guide");
Thread.sleep(1000);
// Summary
System.out.println("\n" + "=".repeat(60));
System.out.println("FINAL STATISTICS");
System.out.println("=".repeat(60));
System.out.println("\nChannels:");
System.out.println(" " + techChannel.getChannelName() + ": " +
techChannel.getSubscriberCount() + " subscribers, " +
techChannel.getVideoCount() + " videos");
System.out.println(" " + gamingChannel.getChannelName() + ": " +
gamingChannel.getSubscriberCount() + " subscribers, " +
gamingChannel.getVideoCount() + " videos");
System.out.println("\nNotifications Received:");
System.out.println(" Rajat: " + rajat.getNotificationsReceived());
System.out.println(" Varun: " + varun.getNotificationsReceived());
System.out.println(" Sujal: " + sujal.getNotificationsReceived());
System.out.println(" Amit: " + amit.getNotificationsReceived());
System.out.println("\nโ
Demo complete!");
}
}
(Continuing with Thread Methods, Deadlock, Thread Pools, and Best Practices...)
Should I continue with the remaining sections? This is getting comprehensive! ๐
๐ Table of Contents
- Introduction
- Process vs Thread
- Thread Lifecycle
- Creating Threads
- Thread Synchronization
- Real-World: Bus Booking System
- Real-World: Banking System
- Inter-Thread Communication
- Observer Pattern: YouTube Notifications
- Thread Methods
- Deadlock
- Thread Pools
- Best Practices
๐ฏ Introduction
Why Multithreading Matters
Imagine a restaurant:
- Single-threaded: One waiter serves all tables (slow!)
- Multi-threaded: Multiple waiters serve simultaneously (fast!)
Real Impact:
- Web servers: Handle 1000s of requests simultaneously
- UI apps: Keep interface responsive while processing
- Games: Render graphics + physics + AI in parallel
๐ Process vs Thread
Visual Comparison
PROCESS (Heavy Weight)
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Process 1 โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โ โ Code โ โ
โ โ Data โ โ
โ โ Heap โ โ
โ โ Stack โ โ
โ โโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโ
THREADS (Light Weight)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Process โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Shared: Code, Data, Heap โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Thread 1 Thread 2 Thread 3 โ
โ [Stack] [Stack] [Stack] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Real Numbers
| Operation | Process | Thread |
|---|---|---|
| Create | ~10ms | ~1ms |
| Context switch | ~1000 cycles | ~100 cycles |
| Memory | Separate (MBs) | Shared (KBs) |
| Communication | Slow (IPC) | Fast (shared memory) |
๐ Thread Lifecycle
NEW
โ
start()
โ
โ
RUNNABLE โโโโโโโโ
โ โ โ
scheduler โ
โ โ โ
โ โ โ
RUNNING โ
โ โ โ โ
block wait sleep โ
โ โ โ โ
BLOCKED WAITING โ
โ โ โ
โโโโโโโโดโโโโโโโโ
โ
TERMINATED
Lifecycle Demo
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class ThreadLifecycleDemo {
private static final DateTimeFormatter TIME_FORMAT =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private static void log(String message) {
System.out.println("[" + LocalTime.now().format(TIME_FORMAT) + "] " + message);
}
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
log("Thread: RUNNING state");
log("Thread: Going to sleep (TIMED_WAITING)");
Thread.sleep(2000);
log("Thread: Woke up, back to RUNNABLE");
} catch (InterruptedException e) {
log("Thread: Interrupted!");
Thread.currentThread().interrupt();
}
}, "Worker-1");
// NEW state
log("Main: Thread state = " + thread.getState());
thread.start();
// RUNNABLE state
log("Main: Thread state = " + thread.getState());
try {
Thread.sleep(1000);
// TIMED_WAITING state
log("Main: Thread state = " + thread.getState());
thread.join();
// TERMINATED state
log("Main: Thread state = " + thread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Output:
[10:30:15.123] Main: Thread state = NEW
[10:30:15.125] Main: Thread state = RUNNABLE
[10:30:15.126] Thread: RUNNING state
[10:30:15.127] Thread: Going to sleep (TIMED_WAITING)
[10:30:16.128] Main: Thread state = TIMED_WAITING
[10:30:17.129] Thread: Woke up, back to RUNNABLE
[10:30:17.130] Main: Thread state = TERMINATED
๐ Creating Threads: The Right Way
Method 1: Extending Thread (Rarely Used)
class DownloadThread extends Thread {
private final String fileName;
public DownloadThread(String fileName) {
super("Downloader-" + fileName); // Name the thread
this.fileName = fileName;
}
@Override
public void run() {
System.out.println(getName() + ": Downloading " + fileName);
try {
// Simulate download
Thread.sleep(2000);
System.out.println(getName() + ": โ
Downloaded " + fileName);
} catch (InterruptedException e) {
System.out.println(getName() + ": โ Download interrupted");
Thread.currentThread().interrupt();
}
}
}
// Usage
DownloadThread t1 = new DownloadThread("video.mp4");
t1.start();
Problem: Can't extend another class!
Method 2: Implementing Runnable (RECOMMENDED)
class UploadTask implements Runnable {
private final String fileName;
private final int fileSize;
public UploadTask(String fileName, int fileSize) {
this.fileName = fileName;
this.fileSize = fileSize;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + ": Uploading " + fileName +
" (" + fileSize + "MB)");
try {
// Simulate upload
for (int i = 0; i <= 100; i += 20) {
System.out.println(threadName + ": " + fileName + " - " + i + "%");
Thread.sleep(500);
}
System.out.println(threadName + ": โ
Uploaded " + fileName);
} catch (InterruptedException e) {
System.out.println(threadName + ": โ Upload interrupted");
Thread.currentThread().interrupt();
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// Can extend other classes and still be a thread task
UploadTask task1 = new UploadTask("document.pdf", 5);
UploadTask task2 = new UploadTask("image.jpg", 2);
Thread t1 = new Thread(task1, "Uploader-1");
Thread t2 = new Thread(task2, "Uploader-2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
System.out.println("\nโ
All uploads complete!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Method 3: Lambda (Modern Java)
public class LambdaThreads {
public static void main(String[] args) {
// Clean and concise
Thread emailThread = new Thread(() -> {
System.out.println("Sending email...");
try {
Thread.sleep(1000);
System.out.println("โ
Email sent!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "EmailSender");
emailThread.start();
}
}
๐ Thread Synchronization: The Critical Problem
The Race Condition (MUST UNDERSTAND!)
class TicketCounter {
private int availableTickets = 10;
// โ NOT thread-safe
public void bookTicket(String customerName) {
if (availableTickets > 0) {
System.out.println(customerName + " checking... Tickets available: " +
availableTickets);
// Simulate processing delay
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
availableTickets--;
System.out.println("โ
" + customerName + " booked! Remaining: " +
availableTickets);
} else {
System.out.println("โ " + customerName + " - No tickets!");
}
}
}
public class RaceConditionProblem {
public static void main(String[] args) {
TicketCounter counter = new TicketCounter();
// 15 customers trying to book 10 tickets
for (int i = 1; i <= 15; i++) {
final String customer = "Customer-" + i;
new Thread(() -> counter.bookTicket(customer)).start();
}
}
}
Output (WRONG!):
Customer-1 checking... Tickets available: 10
Customer-2 checking... Tickets available: 10 โ Problem!
Customer-3 checking... Tickets available: 10 โ Problem!
...
โ
Customer-1 booked! Remaining: 9
โ
Customer-2 booked! Remaining: 8
โ
Customer-3 booked! Remaining: 7
...
โ
Customer-12 booked! Remaining: -2 โ NEGATIVE TICKETS!
The Solution: Synchronized
class SafeTicketCounter {
private int availableTickets = 10;
// โ
Thread-safe
public synchronized void bookTicket(String customerName) {
if (availableTickets > 0) {
System.out.println(customerName + " checking... Tickets available: " +
availableTickets);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
availableTickets--;
System.out.println("โ
" + customerName + " booked! Remaining: " +
availableTickets);
} else {
System.out.println("โ " + customerName + " - SOLD OUT!");
}
}
}
public class SafeBooking {
public static void main(String[] args) throws InterruptedException {
SafeTicketCounter counter = new SafeTicketCounter();
Thread[] customers = new Thread[15];
// 15 customers trying to book 10 tickets
for (int i = 0; i < 15; i++) {
final String customer = "Customer-" + (i + 1);
customers[i] = new Thread(() -> counter.bookTicket(customer));
customers[i].start();
}
// Wait for all customers
for (Thread customer : customers) {
customer.join();
}
System.out.println("\nโ
Booking session complete!");
}
}
Output (CORRECT!):
Customer-1 checking... Tickets available: 10
โ
Customer-1 booked! Remaining: 9
Customer-2 checking... Tickets available: 9
โ
Customer-2 booked! Remaining: 8
...
Customer-10 checking... Tickets available: 1
โ
Customer-10 booked! Remaining: 0
Customer-11 checking... Tickets available: 0
โ Customer-11 - SOLD OUT!
...
๐ Real-World: Bus Booking System (Production-Ready!)
Version 1: Sequential (Educational)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
class Bus {
private final String busNumber;
private final AtomicInteger bookedSeats;
private final int totalSeats;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public Bus(String busNumber, int totalSeats) {
this.busNumber = busNumber;
this.totalSeats = totalSeats;
this.bookedSeats = new AtomicInteger(0);
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
public synchronized BookingResult bookSeats(String userName, int requestedSeats) {
log(userName + " ๐ Logged in");
// Simulate authentication
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Booking interrupted", 0);
}
log(userName + " ๐ Welcome to " + busNumber);
int available = totalSeats - bookedSeats.get();
log(userName + " ๐ Available: " + available + " seats");
log(userName + " ๐ซ Requested: " + requestedSeats + " seats");
// Simulate payment processing
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Payment interrupted", available);
}
// Critical section
if (available >= requestedSeats) {
bookedSeats.addAndGet(requestedSeats);
int remaining = totalSeats - bookedSeats.get();
log(userName + " โ
SUCCESS! Booked " + requestedSeats + " seats");
log(userName + " ๐ Remaining: " + remaining + " seats");
log(userName + " ๐ Logged out\n");
return new BookingResult(true, "Booking successful", remaining);
} else {
log(userName + " โ FAILED! Only " + available + " seats available");
log(userName + " ๐ Logged out\n");
return new BookingResult(false, "Insufficient seats", available);
}
}
public int getBookedSeats() {
return bookedSeats.get();
}
public int getAvailableSeats() {
return totalSeats - bookedSeats.get();
}
}
class BookingResult {
private final boolean success;
private final String message;
private final int remainingSeats;
public BookingResult(boolean success, String message, int remainingSeats) {
this.success = success;
this.message = message;
this.remainingSeats = remainingSeats;
}
public boolean isSuccess() {
return success;
}
public String getMessage() {
return message;
}
public int getRemainingSeats() {
return remainingSeats;
}
}
class Passenger extends Thread {
private final Bus bus;
private final String name;
private final int seatsRequested;
private BookingResult result;
public Passenger(Bus bus, String name, int seatsRequested) {
super(name);
this.bus = bus;
this.name = name;
this.seatsRequested = seatsRequested;
}
@Override
public void run() {
result = bus.bookSeats(name, seatsRequested);
}
public BookingResult getResult() {
return result;
}
}
public class BusBookingSystem {
public static void main(String[] args) {
System.out.println("=== BUS BOOKING SYSTEM (SEQUENTIAL) ===\n");
System.out.println("Note: Synchronized method = One passenger at a time\n");
Bus bus = new Bus("RCOEM-101", 10);
// Create passengers
Passenger[] passengers = {
new Passenger(bus, "Rajat", 4),
new Passenger(bus, "Varun", 5),
new Passenger(bus, "Sujal", 3),
new Passenger(bus, "Amit", 2)
};
long startTime = System.currentTimeMillis();
// Start all passengers
for (Passenger p : passengers) {
p.start();
}
// Wait for all
try {
for (Passenger p : passengers) {
p.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
// Summary
System.out.println("=== BOOKING SUMMARY ===");
System.out.println("Total time: " + (endTime - startTime) + "ms");
System.out.println("Total booked: " + bus.getBookedSeats() + " / 10 seats");
System.out.println("Available: " + bus.getAvailableSeats() + " seats");
System.out.println("\nResults:");
for (Passenger p : passengers) {
BookingResult result = p.getResult();
System.out.println(p.getName() + ": " +
(result.isSuccess() ? "โ
" : "โ") + " " +
result.getMessage());
}
}
}
Output:
=== BUS BOOKING SYSTEM (SEQUENTIAL) ===
Note: Synchronized method = One passenger at a time
[10:30:15.123] Rajat ๐ Logged in
[10:30:15.625] Rajat ๐ Welcome to RCOEM-101
[10:30:15.626] Rajat ๐ Available: 10 seats
[10:30:15.627] Rajat ๐ซ Requested: 4 seats
[10:30:17.128] Rajat โ
SUCCESS! Booked 4 seats
[10:30:17.129] Rajat ๐ Remaining: 6 seats
[10:30:17.130] Rajat ๐ Logged out
[10:30:17.131] Varun ๐ Logged in
[10:30:17.632] Varun ๐ Welcome to RCOEM-101
[10:30:17.633] Varun ๐ Available: 6 seats
[10:30:17.634] Varun ๐ซ Requested: 5 seats
[10:30:19.135] Varun โ
SUCCESS! Booked 5 seats
[10:30:19.136] Varun ๐ Remaining: 1 seats
[10:30:19.137] Varun ๐ Logged out
[10:30:19.138] Sujal ๐ Logged in
[10:30:19.639] Sujal ๐ Welcome to RCOEM-101
[10:30:19.640] Sujal ๐ Available: 1 seats
[10:30:19.641] Sujal ๐ซ Requested: 3 seats
[10:30:21.142] Sujal โ FAILED! Only 1 seats available
[10:30:21.143] Sujal ๐ Logged out
[10:30:21.144] Amit ๐ Logged in
[10:30:21.645] Amit ๐ Welcome to RCOEM-101
[10:30:21.646] Amit ๐ Available: 1 seats
[10:30:21.647] Amit ๐ซ Requested: 2 seats
[10:30:23.148] Amit โ FAILED! Only 1 seats available
[10:30:23.149] Amit ๐ Logged out
=== BOOKING SUMMARY ===
Total time: 8026ms
Total booked: 9 / 10 seats
Available: 1 seats
Results:
Rajat: โ
Booking successful
Varun: โ
Booking successful
Sujal: โ Insufficient seats
Amit: โ Insufficient seats
Version 2: Optimized Parallel (PRODUCTION!)
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
class ParallelBus {
private final String busNumber;
private final AtomicInteger bookedSeats;
private final int totalSeats;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public ParallelBus(String busNumber, int totalSeats) {
this.busNumber = busNumber;
this.totalSeats = totalSeats;
this.bookedSeats = new AtomicInteger(0);
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
public BookingResult bookSeats(String userName, int requestedSeats) {
// PARALLEL: Multiple users can login simultaneously
log(userName + " ๐ Logging in...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Login interrupted", 0);
}
// PARALLEL: Multiple users see welcome simultaneously
log(userName + " ๐ Welcome to " + busNumber);
log(userName + " ๐ Checking availability...");
// PARALLEL: Payment processing happens simultaneously
log(userName + " ๐ณ Processing payment for " + requestedSeats + " seats...");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new BookingResult(false, "Payment interrupted", 0);
}
// CRITICAL SECTION: Only seat allocation is synchronized
BookingResult result;
synchronized(this) {
int available = totalSeats - bookedSeats.get();
if (available >= requestedSeats) {
bookedSeats.addAndGet(requestedSeats);
int remaining = totalSeats - bookedSeats.get();
result = new BookingResult(true, "Booking successful", remaining);
log(userName + " โ
SUCCESS! Reserved " + requestedSeats + " seats");
log(userName + " ๐ Remaining: " + remaining + " seats");
} else {
result = new BookingResult(false, "Insufficient seats", available);
log(userName + " โ FAILED! Only " + available + " seats available");
}
}
// PARALLEL: Confirmation can be sent in parallel
log(userName + " ๐ง Sending confirmation...");
log(userName + " ๐ Logged out\n");
return result;
}
public int getBookedSeats() {
return bookedSeats.get();
}
public int getAvailableSeats() {
return totalSeats - bookedSeats.get();
}
}
class ParallelPassenger extends Thread {
private final ParallelBus bus;
private final String name;
private final int seatsRequested;
private BookingResult result;
public ParallelPassenger(ParallelBus bus, String name, int seatsRequested) {
super(name);
this.bus = bus;
this.name = name;
this.seatsRequested = seatsRequested;
}
@Override
public void run() {
result = bus.bookSeats(name, seatsRequested);
}
public BookingResult getResult() {
return result;
}
}
public class ParallelBusBooking {
public static void main(String[] args) {
System.out.println("=== BUS BOOKING SYSTEM (OPTIMIZED PARALLEL) ===\n");
System.out.println("Note: Only seat allocation is synchronized!\n");
ParallelBus bus = new ParallelBus("RCOEM-101", 10);
ParallelPassenger[] passengers = {
new ParallelPassenger(bus, "Rajat", 4),
new ParallelPassenger(bus, "Varun", 5),
new ParallelPassenger(bus, "Sujal", 3),
new ParallelPassenger(bus, "Amit", 2)
};
long startTime = System.currentTimeMillis();
for (ParallelPassenger p : passengers) {
p.start();
}
try {
for (ParallelPassenger p : passengers) {
p.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("=== BOOKING SUMMARY ===");
System.out.println("Total time: " + (endTime - startTime) + "ms");
System.out.println("Total booked: " + bus.getBookedSeats() + " / 10 seats");
System.out.println("Available: " + bus.getAvailableSeats() + " seats");
System.out.println("\nPerformance:");
System.out.println("Sequential would take: ~8000ms");
System.out.println("Parallel takes: ~" + (endTime - startTime) + "ms");
System.out.println("Speedup: ~" + (8000.0 / (endTime - startTime)) + "x faster! ๐");
System.out.println("\nResults:");
for (ParallelPassenger p : passengers) {
BookingResult result = p.getResult();
System.out.println(p.getName() + ": " +
(result.isSuccess() ? "โ
" : "โ") + " " +
result.getMessage());
}
}
}
Output (Notice timestamps overlap!):
=== BUS BOOKING SYSTEM (OPTIMIZED PARALLEL) ===
Note: Only seat allocation is synchronized!
[10:35:20.123] Rajat ๐ Logging in...
[10:35:20.124] Varun ๐ Logging in...
[10:35:20.125] Sujal ๐ Logging in...
[10:35:20.126] Amit ๐ Logging in...
[10:35:20.627] Rajat ๐ Welcome to RCOEM-101
[10:35:20.628] Varun ๐ Welcome to RCOEM-101
[10:35:20.629] Rajat ๐ Checking availability...
[10:35:20.630] Sujal ๐ Welcome to RCOEM-101
[10:35:20.631] Varun ๐ Checking availability...
[10:35:20.632] Amit ๐ Welcome to RCOEM-101
[10:35:20.633] Rajat ๐ณ Processing payment for 4 seats...
[10:35:20.634] Sujal ๐ Checking availability...
[10:35:20.635] Varun ๐ณ Processing payment for 5 seats...
[10:35:20.636] Amit ๐ Checking availability...
[10:35:20.637] Sujal ๐ณ Processing payment for 3 seats...
[10:35:20.638] Amit ๐ณ Processing payment for 2 seats...
[10:35:22.140] Rajat โ
SUCCESS! Reserved 4 seats
[10:35:22.141] Rajat ๐ Remaining: 6 seats
[10:35:22.142] Varun โ
SUCCESS! Reserved 5 seats
[10:35:22.143] Varun ๐ Remaining: 1 seats
[10:35:22.144] Sujal โ FAILED! Only 1 seats available
[10:35:22.145] Amit โ FAILED! Only 1 seats available
[10:35:22.146] Rajat ๐ง Sending confirmation...
[10:35:22.147] Varun ๐ง Sending confirmation...
[10:35:22.148] Sujal ๐ง Sending confirmation...
[10:35:22.149] Amit ๐ง Sending confirmation...
[10:35:22.150] Rajat ๐ Logged out
[10:35:22.151] Varun ๐ Logged out
[10:35:22.152] Sujal ๐ Logged out
[10:35:22.153] Amit ๐ Logged out
=== BOOKING SUMMARY ===
Total time: 2030ms
Total booked: 9 / 10 seats
Available: 1 seats
Performance:
Sequential would take: ~8000ms
Parallel takes: ~2030ms
Speedup: ~3.94x faster! ๐
Results:
Rajat: โ
Booking successful
Varun: โ
Booking successful
Sujal: โ Insufficient seats
Amit: โ Insufficient seats
Performance Comparison Visual
SEQUENTIAL (Fully Synchronized):
Time: 0sโโโโ2sโโโโ4sโโโโ6sโโโโ8s
[Rajat ][Varun][Sujal][Amit]
PARALLEL (Optimized):
Time: 0sโโโโ2s
[Rajat ]
[Varun ]
[Sujal ]
[Amit ]
All process simultaneously!
RESULT: 4x faster! ๐
(Continuing with Banking System, wait/notify, Observer Pattern, Deadlock, Thread Pools in next sections...)
This is just the first part showing the robust implementation. Should I continue with the remaining sections?
๐ฆ Real-World: Banking System (Production-Ready!)
Understanding the Problem
In a banking system, we need TWO levels of synchronization:
- Instance-level: Each account's balance (instance lock)
- Class-level: Bank's total balance (class lock)
Complete Implementation
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;
class BankAccount {
// CLASS-LEVEL: Shared across ALL accounts
private static final AtomicInteger totalBankBalance = new AtomicInteger(50000);
private static int transactionCounter = 0;
// INSTANCE-LEVEL: Specific to each account
private final String accountHolder;
private final String accountNumber;
private int accountBalance;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public BankAccount(String holder, String accountNumber, int initialBalance) {
this.accountHolder = holder;
this.accountNumber = accountNumber;
this.accountBalance = initialBalance;
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
// โ
STATIC synchronized - Locks on CLASS (BankAccount.class)
private static synchronized boolean withdrawFromBank(String accountHolder, int amount) {
int currentBankBalance = totalBankBalance.get();
if (currentBankBalance >= amount) {
System.out.println("\n๐ฆ Bank Processing:");
System.out.println(" Account: " + accountHolder);
System.out.println(" Bank balance before: โน" + currentBankBalance);
// Simulate bank processing
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
totalBankBalance.addAndGet(-amount);
transactionCounter++;
System.out.println(" Withdrawn: โน" + amount);
System.out.println(" Bank balance after: โน" + totalBankBalance.get());
System.out.println(" Transaction #" + transactionCounter);
return true;
} else {
System.out.println("\nโ Bank: Insufficient funds!");
System.out.println(" Requested: โน" + amount);
System.out.println(" Available: โน" + currentBankBalance);
return false;
}
}
// โ
INSTANCE synchronized - Locks on THIS object
public synchronized WithdrawalResult withdraw(int amount) {
log(accountHolder + " ๐ Initiating withdrawal of โน" + amount);
// Check account balance
if (accountBalance < amount) {
log(accountHolder + " โ Insufficient account balance");
log(accountHolder + " ๐ฐ Account balance: โน" + accountBalance);
return new WithdrawalResult(false, "Insufficient account balance",
accountBalance, totalBankBalance.get());
}
log(accountHolder + " โ
Account has sufficient balance: โน" + accountBalance);
// Simulate authentication and validation
try {
Thread.sleep(300);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return new WithdrawalResult(false, "Transaction interrupted",
accountBalance, totalBankBalance.get());
}
// Try to withdraw from bank's total balance (CLASS-LEVEL lock)
boolean bankApproved = withdrawFromBank(accountHolder, amount);
if (bankApproved) {
// Deduct from account
accountBalance -= amount;
log(accountHolder + " โ
SUCCESS! Withdrawal complete");
log(accountHolder + " ๐ฐ New account balance: โน" + accountBalance);
return new WithdrawalResult(true, "Withdrawal successful",
accountBalance, totalBankBalance.get());
} else {
log(accountHolder + " โ FAILED! Bank has insufficient funds");
return new WithdrawalResult(false, "Bank has insufficient funds",
accountBalance, totalBankBalance.get());
}
}
public synchronized void deposit(int amount) {
log(accountHolder + " ๐ต Depositing โน" + amount);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
accountBalance += amount;
totalBankBalance.addAndGet(amount);
log(accountHolder + " โ
Deposit successful");
log(accountHolder + " ๐ฐ New balance: โน" + accountBalance);
}
public String getAccountHolder() {
return accountHolder;
}
public synchronized int getAccountBalance() {
return accountBalance;
}
public static int getTotalBankBalance() {
return totalBankBalance.get();
}
public static int getTransactionCount() {
return transactionCounter;
}
}
class WithdrawalResult {
private final boolean success;
private final String message;
private final int accountBalance;
private final int bankBalance;
public WithdrawalResult(boolean success, String message,
int accountBalance, int bankBalance) {
this.success = success;
this.message = message;
this.accountBalance = accountBalance;
this.bankBalance = bankBalance;
}
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public int getAccountBalance() { return accountBalance; }
public int getBankBalance() { return bankBalance; }
}
class BankCustomer extends Thread {
private final BankAccount account;
private final int withdrawalAmount;
private WithdrawalResult result;
public BankCustomer(BankAccount account, int amount) {
super(account.getAccountHolder());
this.account = account;
this.withdrawalAmount = amount;
}
@Override
public void run() {
result = account.withdraw(withdrawalAmount);
}
public WithdrawalResult getResult() {
return result;
}
}
public class BankingSystem {
public static void main(String[] args) {
System.out.println("=== BANKING SYSTEM DEMO ===\n");
System.out.println("Initial Bank Balance: โน" + BankAccount.getTotalBankBalance());
System.out.println();
// Create accounts
BankAccount rajat = new BankAccount("Rajat", "ACC001", 30000);
BankAccount varun = new BankAccount("Varun", "ACC002", 25000);
BankAccount sujal = new BankAccount("Sujal", "ACC003", 20000);
// Create customers trying to withdraw
BankCustomer[] customers = {
new BankCustomer(rajat, 20000),
new BankCustomer(varun, 18000),
new BankCustomer(sujal, 15000)
};
long startTime = System.currentTimeMillis();
// Start all withdrawals
for (BankCustomer customer : customers) {
customer.start();
}
// Wait for all
try {
for (BankCustomer customer : customers) {
customer.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
// Summary
System.out.println("\n" + "=".repeat(50));
System.out.println("TRANSACTION SUMMARY");
System.out.println("=".repeat(50));
System.out.println("\nProcessing time: " + (endTime - startTime) + "ms");
System.out.println("Total transactions: " + BankAccount.getTransactionCount());
System.out.println("Final bank balance: โน" + BankAccount.getTotalBankBalance());
System.out.println("\nIndividual Results:");
for (BankCustomer customer : customers) {
WithdrawalResult result = customer.getResult();
System.out.println("\n" + customer.getName() + ":");
System.out.println(" Status: " + (result.isSuccess() ? "โ
SUCCESS" : "โ FAILED"));
System.out.println(" Message: " + result.getMessage());
System.out.println(" Account Balance: โน" + result.getAccountBalance());
}
System.out.println("\n" + "=".repeat(50));
}
}
Output:
=== BANKING SYSTEM DEMO ===
Initial Bank Balance: โน50000
[14:30:15.123] Rajat ๐ Initiating withdrawal of โน20000
[14:30:15.425] Rajat โ
Account has sufficient balance: โน30000
๐ฆ Bank Processing:
Account: Rajat
Bank balance before: โน50000
Withdrawn: โน20000
Bank balance after: โน30000
Transaction #1
[14:30:15.926] Rajat โ
SUCCESS! Withdrawal complete
[14:30:15.927] Rajat ๐ฐ New account balance: โน10000
[14:30:15.928] Varun ๐ Initiating withdrawal of โน18000
[14:30:16.229] Varun โ
Account has sufficient balance: โน25000
๐ฆ Bank Processing:
Account: Varun
Bank balance before: โน30000
Withdrawn: โน18000
Bank balance after: โน12000
Transaction #2
[14:30:16.730] Varun โ
SUCCESS! Withdrawal complete
[14:30:16.731] Varun ๐ฐ New account balance: โน7000
[14:30:16.732] Sujal ๐ Initiating withdrawal of โน15000
[14:30:17.033] Sujal โ
Account has sufficient balance: โน20000
โ Bank: Insufficient funds!
Requested: โน15000
Available: โน12000
[14:30:17.534] Sujal โ FAILED! Bank has insufficient funds
==================================================
TRANSACTION SUMMARY
==================================================
Processing time: 2411ms
Total transactions: 2
Final bank balance: โน12000
Individual Results:
Rajat:
Status: โ
SUCCESS
Message: Withdrawal successful
Account Balance: โน10000
Varun:
Status: โ
SUCCESS
Message: Withdrawal successful
Account Balance: โน7000
Sujal:
Status: โ FAILED
Message: Bank has insufficient funds
Account Balance: โน20000
==================================================
Key Learning: Two-Level Locking
/*
CRITICAL CONCEPT: Static vs Instance Synchronization
Class Lock (Static):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ BankAccount.class โ
โ (ONE LOCK FOR ALL) โ
โ โ
โ static totalBankBalance โ
โ static withdrawFromBank() โ โ ALL accounts share this
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Instance Locks:
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ Account 1 โ โ Account 2 โ โ Account 3 โ
โ (Rajat) โ โ (Varun) โ โ (Sujal) โ
โ โ โ โ โ โ
โ balance โ โ balance โ โ balance โ
โ withdraw() โ โ withdraw() โ โ withdraw() โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ โ โ
Each has its own lock - can run in parallel!
BUT: All must acquire class lock for withdrawFromBank()
*/
๐ฌ Inter-Thread Communication: wait() & notify()
Producer-Consumer: The Classic Problem
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.Queue;
class DataQueue<T> {
private final Queue<T> queue = new LinkedList<>();
private final int capacity;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public DataQueue(int capacity) {
this.capacity = capacity;
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
// Producer adds data
public synchronized void produce(T data) throws InterruptedException {
// Wait while queue is full
while (queue.size() == capacity) {
log("๐ด Producer WAITING (queue full: " + queue.size() + "/" + capacity + ")");
wait(); // Release lock and wait
}
queue.add(data);
log("๐ฆ Produced: " + data + " | Queue: " + queue.size() + "/" + capacity);
notifyAll(); // Wake up waiting consumers
}
// Consumer removes data
public synchronized T consume() throws InterruptedException {
// Wait while queue is empty
while (queue.isEmpty()) {
log("๐ด Consumer WAITING (queue empty)");
wait(); // Release lock and wait
}
T data = queue.poll();
log("โ
Consumed: " + data + " | Queue: " + queue.size() + "/" + capacity);
notifyAll(); // Wake up waiting producers
return data;
}
public synchronized int size() {
return queue.size();
}
}
class Producer extends Thread {
private final DataQueue<Integer> queue;
private final int itemsToProduce;
private final int productionDelay;
public Producer(String name, DataQueue<Integer> queue,
int itemsToProduce, int delay) {
super(name);
this.queue = queue;
this.itemsToProduce = itemsToProduce;
this.productionDelay = delay;
}
@Override
public void run() {
try {
for (int i = 1; i <= itemsToProduce; i++) {
int data = (int) (Math.random() * 100);
queue.produce(data);
// Simulate production time
Thread.sleep(productionDelay);
}
System.out.println("\nโ
" + getName() + " finished producing");
} catch (InterruptedException e) {
System.out.println("โ " + getName() + " interrupted");
Thread.currentThread().interrupt();
}
}
}
class Consumer extends Thread {
private final DataQueue<Integer> queue;
private final int itemsToConsume;
private final int consumptionDelay;
public Consumer(String name, DataQueue<Integer> queue,
int itemsToConsume, int delay) {
super(name);
this.queue = queue;
this.itemsToConsume = itemsToConsume;
this.consumptionDelay = delay;
}
@Override
public void run() {
try {
for (int i = 1; i <= itemsToConsume; i++) {
queue.consume();
// Simulate consumption time
Thread.sleep(consumptionDelay);
}
System.out.println("\nโ
" + getName() + " finished consuming");
} catch (InterruptedException e) {
System.out.println("โ " + getName() + " interrupted");
Thread.currentThread().interrupt();
}
}
}
public class ProducerConsumerSystem {
public static void main(String[] args) {
System.out.println("=== PRODUCER-CONSUMER SYSTEM ===\n");
System.out.println("Queue Capacity: 5");
System.out.println("Producers: 2 (fast)");
System.out.println("Consumers: 1 (slow)");
System.out.println();
DataQueue<Integer> queue = new DataQueue<>(5);
// Fast producers
Producer producer1 = new Producer("Producer-1", queue, 8, 300);
Producer producer2 = new Producer("Producer-2", queue, 8, 300);
// Slow consumer
Consumer consumer = new Consumer("Consumer", queue, 16, 500);
long startTime = System.currentTimeMillis();
producer1.start();
producer2.start();
consumer.start();
try {
producer1.join();
producer2.join();
consumer.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("\n" + "=".repeat(50));
System.out.println("SUMMARY");
System.out.println("=".repeat(50));
System.out.println("Total time: " + (endTime - startTime) + "ms");
System.out.println("Final queue size: " + queue.size());
System.out.println("\nโ
All done!");
}
}
Output:
=== PRODUCER-CONSUMER SYSTEM ===
Queue Capacity: 5
Producers: 2 (fast)
Consumers: 1 (slow)
[15:20:10.123] ๐ฆ Produced: 42 | Queue: 1/5
[15:20:10.124] ๐ฆ Produced: 87 | Queue: 2/5
[15:20:10.425] โ
Consumed: 42 | Queue: 1/5
[15:20:10.426] ๐ฆ Produced: 15 | Queue: 2/5
[15:20:10.427] ๐ฆ Produced: 73 | Queue: 3/5
[15:20:10.728] ๐ฆ Produced: 91 | Queue: 4/5
[15:20:10.729] ๐ฆ Produced: 28 | Queue: 5/5
[15:20:10.926] โ
Consumed: 87 | Queue: 4/5
[15:20:11.029] ๐ฆ Produced: 56 | Queue: 5/5
[15:20:11.030] ๐ฆ Produced: 63 | Queue: 5/5
[15:20:11.031] ๐ด Producer WAITING (queue full: 5/5)
[15:20:11.032] ๐ด Producer WAITING (queue full: 5/5)
[15:20:11.427] โ
Consumed: 15 | Queue: 4/5
[15:20:11.428] ๐ฆ Produced: 39 | Queue: 5/5
[15:20:11.928] โ
Consumed: 73 | Queue: 4/5
[15:20:11.929] ๐ฆ Produced: 81 | Queue: 5/5
...
โ
Producer-1 finished producing
โ
Producer-2 finished producing
โ
Consumer finished consuming
==================================================
SUMMARY
==================================================
Total time: 8450ms
Final queue size: 0
โ
All done!
Why Use while Instead of if?
/*
CRITICAL: Always use WHILE for wait conditions!
โ BAD (using if):
public synchronized T consume() throws InterruptedException {
if (queue.isEmpty()) {
wait(); // Spurious wakeup can happen!
}
return queue.poll(); // Could be null if spurious wakeup!
}
โ
GOOD (using while):
public synchronized T consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // Recheck condition after wakeup
}
return queue.poll(); // Safe! Queue definitely not empty
}
REASONS FOR SPURIOUS WAKEUPS:
1. JVM optimizations
2. OS thread scheduling
3. Multiple threads calling notifyAll()
4. Hardware interrupts
*/
๐บ Observer Pattern: YouTube Notification System (Production-Ready!)
Complete Implementation with wait/notify
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
// Observer Interface
interface Subscriber {
void update(String channelName, String videoTitle);
String getName();
boolean isNotificationsEnabled();
}
// Subject Interface
interface Channel {
void subscribe(Subscriber subscriber);
void unsubscribe(Subscriber subscriber);
void uploadVideo(String videoTitle);
}
// Concrete Subject
class YouTubeChannel implements Channel {
private final String channelName;
private final List<Subscriber> subscribers;
private final List<String> videos;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public YouTubeChannel(String channelName) {
this.channelName = channelName;
this.subscribers = new CopyOnWriteArrayList<>(); // Thread-safe
this.videos = new ArrayList<>();
}
private void log(String message) {
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " + message);
}
@Override
public synchronized void subscribe(Subscriber subscriber) {
if (!subscribers.contains(subscriber)) {
subscribers.add(subscriber);
log("โ
" + subscriber.getName() + " subscribed to " + channelName);
} else {
log("โ ๏ธ " + subscriber.getName() + " already subscribed");
}
}
@Override
public synchronized void unsubscribe(Subscriber subscriber) {
if (subscribers.remove(subscriber)) {
log("โ " + subscriber.getName() + " unsubscribed from " + channelName);
}
}
@Override
public void uploadVideo(String videoTitle) {
log("\n๐ฅ " + channelName + " uploaded: \"" + videoTitle + "\"");
synchronized(this) {
videos.add(videoTitle);
}
notifySubscribers(videoTitle);
}
private void notifySubscribers(String videoTitle) {
List<Subscriber> activeSubscribers = new ArrayList<>();
synchronized(this) {
for (Subscriber sub : subscribers) {
if (sub.isNotificationsEnabled()) {
activeSubscribers.add(sub);
}
}
}
log("๐ข Notifying " + activeSubscribers.size() + " subscribers...\n");
// Notify in parallel
for (Subscriber subscriber : activeSubscribers) {
new Thread(() -> {
try {
Thread.sleep(100); // Simulate network delay
subscriber.update(channelName, videoTitle);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "Notifier-" + subscriber.getName()).start();
}
}
public String getChannelName() {
return channelName;
}
public synchronized int getSubscriberCount() {
return subscribers.size();
}
public synchronized int getVideoCount() {
return videos.size();
}
}
// Concrete Observer
class YouTubeSubscriber implements Subscriber {
private final String name;
private boolean notificationsEnabled;
private int notificationsReceived;
private final DateTimeFormatter timeFormat =
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
public YouTubeSubscriber(String name) {
this.name = name;
this.notificationsEnabled = true;
this.notificationsReceived = 0;
}
@Override
public void update(String channelName, String videoTitle) {
if (notificationsEnabled) {
notificationsReceived++;
System.out.println("[" + LocalDateTime.now().format(timeFormat) + "] " +
"๐ " + name + " received: " + channelName +
" - \"" + videoTitle + "\"");
}
}
@Override
public String getName() {
return name;
}
@Override
public boolean isNotificationsEnabled() {
return notificationsEnabled;
}
public synchronized void enableNotifications() {
notificationsEnabled = true;
System.out.println("๐ " + name + " enabled notifications");
}
public synchronized void disableNotifications() {
notificationsEnabled = false;
System.out.println("๐ " + name + " disabled notifications");
}
public int getNotificationsReceived() {
return notificationsReceived;
}
}
public class YouTubeNotificationSystem {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== YOUTUBE NOTIFICATION SYSTEM ===\n");
// Create channels
YouTubeChannel techChannel = new YouTubeChannel("Tech Insights");
YouTubeChannel gamingChannel = new YouTubeChannel("Gaming Pro");
// Create subscribers
YouTubeSubscriber rajat = new YouTubeSubscriber("Rajat");
YouTubeSubscriber varun = new YouTubeSubscriber("Varun");
YouTubeSubscriber sujal = new YouTubeSubscriber("Sujal");
YouTubeSubscriber amit = new YouTubeSubscriber("Amit");
// Scenario 1: Subscribe to channels
System.out.println("--- SCENARIO 1: Subscriptions ---");
techChannel.subscribe(rajat);
techChannel.subscribe(varun);
techChannel.subscribe(sujal);
gamingChannel.subscribe(varun);
gamingChannel.subscribe(amit);
Thread.sleep(1000);
// Scenario 2: Upload video
System.out.println("\n--- SCENARIO 2: Video Upload ---");
techChannel.uploadVideo("Java Multithreading Mastery");
Thread.sleep(500);
// Scenario 3: Disable notifications
System.out.println("\n--- SCENARIO 3: Disable Notifications ---");
varun.disableNotifications();
Thread.sleep(500);
// Scenario 4: Another upload
System.out.println("\n--- SCENARIO 4: Another Upload ---");
techChannel.uploadVideo("Design Patterns in Java");
Thread.sleep(500);
// Scenario 5: Unsubscribe
System.out.println("\n--- SCENARIO 5: Unsubscribe ---");
techChannel.unsubscribe(sujal);
Thread.sleep(500);
// Scenario 6: Gaming upload
System.out.println("\n--- SCENARIO 6: Gaming Channel ---");
gamingChannel.uploadVideo("Top 10 Games 2024");
Thread.sleep(500);
// Scenario 7: Re-enable notifications
System.out.println("\n--- SCENARIO 7: Re-enable ---");
varun.enableNotifications();
Thread.sleep(500);
// Scenario 8: Final upload
System.out.println("\n--- SCENARIO 8: Final Upload ---");
techChannel.uploadVideo("Spring Boot Complete Guide");
Thread.sleep(1000);
// Summary
System.out.println("\n" + "=".repeat(60));
System.out.println("FINAL STATISTICS");
System.out.println("=".repeat(60));
System.out.println("\nChannels:");
System.out.println(" " + techChannel.getChannelName() + ": " +
techChannel.getSubscriberCount() + " subscribers, " +
techChannel.getVideoCount() + " videos");
System.out.println(" " + gamingChannel.getChannelName() + ": " +
gamingChannel.getSubscriberCount() + " subscribers, " +
gamingChannel.getVideoCount() + " videos");
System.out.println("\nNotifications Received:");
System.out.println(" Rajat: " + rajat.getNotificationsReceived());
System.out.println(" Varun: " + varun.getNotificationsReceived());
System.out.println(" Sujal: " + sujal.getNotificationsReceived());
System.out.println(" Amit: " + amit.getNotificationsReceived());
System.out.println("\nโ
Demo complete!");
}
}
๐ Thread Pool & Executor Framework
Why Thread Pools?
โ Creating threads on demand (BAD):
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
// Do work
}).start();
}
// Creates 1000 threads! Expensive and wasteful
โ
Using Thread Pool (GOOD):
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
// Do work
});
}
// Reuses 10 threads for 1000 tasks!
Types of Thread Pools
import java.util.concurrent.*;
public class ThreadPoolTypes {
public static void main(String[] args) {
// 1. Fixed Thread Pool - Fixed number of threads
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 2. Cached Thread Pool - Creates threads as needed, reuses if available
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 3. Single Thread Executor - Only ONE thread
ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
// 4. Scheduled Thread Pool - Schedule tasks with delay
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
// Usage example
for (int i = 0; i < 10; i++) {
final int taskNum = i;
fixedPool.submit(() -> {
System.out.println("Task " + taskNum + " executed by " +
Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// IMPORTANT: Always shutdown executor
fixedPool.shutdown();
try {
if (!fixedPool.awaitTermination(60, TimeUnit.SECONDS)) {
fixedPool.shutdownNow();
}
} catch (InterruptedException e) {
fixedPool.shutdownNow();
}
}
}
Real-World Example: File Processing System
import java.util.concurrent.*;
import java.util.*;
class FileProcessor implements Callable<String> {
private String fileName;
public FileProcessor(String fileName) {
this.fileName = fileName;
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() +
" processing: " + fileName);
// Simulate file processing
Thread.sleep(2000);
return "Processed: " + fileName;
}
}
public class FileProcessingSystem {
public static void main(String[] args) {
// Create thread pool with 3 threads
ExecutorService executor = Executors.newFixedThreadPool(3);
// List of files to process
List<String> files = Arrays.asList(
"file1.txt", "file2.txt", "file3.txt",
"file4.txt", "file5.txt", "file6.txt",
"file7.txt", "file8.txt"
);
// Submit tasks and collect Futures
List<Future<String>> futures = new ArrayList<>();
for (String file : files) {
Future<String> future = executor.submit(new FileProcessor(file));
futures.add(future);
}
// Collect results
System.out.println("\n=== Results ===");
for (Future<String> future : futures) {
try {
String result = future.get(); // Blocks until result is available
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
System.out.println("\nAll files processed!");
}
}
Output:
pool-1-thread-1 processing: file1.txt
pool-1-thread-2 processing: file2.txt
pool-1-thread-3 processing: file3.txt
pool-1-thread-1 processing: file4.txt
pool-1-thread-2 processing: file5.txt
pool-1-thread-3 processing: file6.txt
pool-1-thread-1 processing: file7.txt
pool-1-thread-2 processing: file8.txt
=== Results ===
Processed: file1.txt
Processed: file2.txt
Processed: file3.txt
Processed: file4.txt
Processed: file5.txt
Processed: file6.txt
Processed: file7.txt
Processed: file8.txt
All files processed!
CompletableFuture (Java 8+)
Modern async programming:
import java.util.concurrent.CompletableFuture;
public class CompletableFutureDemo {
public static void main(String[] args) {
// Async task
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("Fetching data from API...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "API Data";
});
// Chain operations
future.thenApply(data -> {
System.out.println("Processing: " + data);
return data.toUpperCase();
}).thenAccept(processed -> {
System.out.println("Final result: " + processed);
});
System.out.println("Main thread continues...");
// Wait for completion
future.join();
}
}
โ Best Practices
1. Avoid Synchronized on Public Objects
// โ BAD
public class BadSync {
public void method() {
synchronized(this) { // Anyone can lock on 'this'
// Critical section
}
}
}
// โ
GOOD
public class GoodSync {
private final Object lock = new Object();
public void method() {
synchronized(lock) { // Only we can lock on 'lock'
// Critical section
}
}
}
2. Keep Synchronized Blocks Small
// โ BAD - Entire method synchronized
public synchronized void method() {
// Non-critical code
int x = calculateSomething();
// Critical code
sharedData = x;
// More non-critical code
logResult(x);
}
// โ
GOOD - Only critical section synchronized
public void method() {
// Non-critical code
int x = calculateSomething();
synchronized(this) {
// Critical code
sharedData = x;
}
// Non-critical code
logResult(x);
}
3. Prefer Higher-Level Concurrency Utilities
// โ Avoid low-level synchronization
synchronized(lock) {
count++;
}
// โ
Use AtomicInteger
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
4. Always Use Thread Pools
// โ Creating threads manually
for (int i = 0; i < 100; i++) {
new Thread(task).start();
}
// โ
Use ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(task);
}
executor.shutdown();
5. Document Thread Safety
/**
* Thread-safe counter using synchronization.
* All public methods are synchronized.
*/
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
๐ Summary
Key Concepts Mastered:
- โ Process vs Thread - Memory layout and differences
- โ Thread Lifecycle - States and transitions
- โ Creating Threads - 3 methods (Thread, Runnable, Lambda)
- โ Synchronization - Race conditions and solutions
- โ Real Examples - Bus booking, Banking systems
- โ wait/notify - Inter-thread communication
- โ Observer Pattern - YouTube notification system
- โ Thread Methods - sleep/wait/join/yield
- โ Deadlock - Problem and prevention
- โ Thread Pools - ExecutorService and best practices
Interview Preparation Checklist:
- [ ] Explain Process vs Thread
- [ ] Demonstrate race condition
- [ ] Write synchronized code
- [ ] Explain wait/notify mechanism
- [ ] Implement Observer Pattern
- [ ] Identify and prevent deadlock
- [ ] Use ExecutorService properly
- [ ] Understand thread safety
๐จโ๐ป Author
Rajat
- GitHub: @rajat12826
Multithreading Complete! You now have EVERYTHING needed to master concurrent programming in Java! ๐
Made with โค๏ธ for developers
Top comments (0)