BIG Thanks to www.geeksforgeeks.org​


Synchronization ensures that only ONE thread can access shared resource (like a variable, object, or method) at a time. It prevents concurrent threads from interfering with each other while modifying shared data.



Why is Synchronization Needed?

  • Prevents Data Inconsistency: Ensures that multiple threads don’t corrupt shared data when accessing it simultaneously.
  • Avoids Race Conditions: Allows only one thread to execute a critical section at a time, maintaining predictable results.
  • Maintains Thread Safety: Protects shared resources from concurrent modification by multiple threads.
  • Ensures Data Integrity: Keeps shared data accurate and consistent throughout program execution.


Types of Synchronization

1. Process Synchronization

three distinct, runnable examples.

These examples cover different aspects of synchronization: Synchronized Methods, Synchronized Blocks, and Static Synchronization (locking on the class level).

Here are the three examples:

  1. ATM Transaction (Synchronized Method): Prevents two people from withdrawing more money than exists in a shared account at the exact same time.

  2. Movie Ticket Booking (Synchronized Block): Shows how to lock only a specific part of the code (critical section) to keep the rest of the application fast.

  3. Office Printer (Object Level Lock): Ensures that when one employee is printing a document, another employee's pages don't get mixed in the middle.




1. ATMTransaction.java: Focuses on Synchronized Methods. It handles the classic "race condition" where two people try to withdraw money simultaneously.​

/**
* Real-World Example 1: Joint Bank Account
* Concept: Synchronized Methods
*
* Scenario: A husband and wife have a joint bank account. They both try to
* withdraw money at the exact same time from different ATMs.
* * Without Synchronization: They might both see a balance of $100, both withdraw $100,
* and the bank loses money (Race Condition).
* * With Synchronization: The 'withdraw' method is locked. If the husband is executing it,
* the wife's thread must wait until he finishes.
*/
class BankAccount {
private int balance = 1000; // Initial balance
// 'synchronized' keyword ensures only one thread can access this method at a time
public synchronized void withdraw(String name, int amount) {
System.out.println(name + " is checking balance...");

if (balance >= amount) {
System.out.println(name + " sees sufficient balance: $" + balance);

try {
// Simulate processing time (e.g., counting cash, network delay)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

balance = balance - amount;
System.out.println(name + " withdrew $" + amount);
System.out.println("Remaining Balance: $" + balance);
} else {
System.out.println(name + " tried to withdraw $" + amount + " but Insufficient Funds. Balance: $" + balance);
}
System.out.println("--------------------------------------------------");
}
}
class User extends Thread {
BankAccount account;
String name;
int amount;
User(BankAccount account, String name, int amount) {
this.account = account;
this.name = name;
this.amount = amount;
}
public void run() {
account.withdraw(name, amount);
}
}
public class ATMTransaction {
public static void main(String[] args) {
BankAccount sharedAccount = new BankAccount();
// Both try to withdraw 700. Total need is 1400, but account only has 1000.
// Without sync, both might succeed. With sync, only one will succeed.
User husband = new User(sharedAccount, "Husband", 700);
User wife = new User(sharedAccount, "Wife", 700);
husband.start();
wife.start();
}
}


​2. MovieTheaterSeats.java: Focuses on Synchronized Blocks. This is a more efficient approach. Notice how the user enters the system (logging in, looking around) without locking the system. The lock only happens at the critical moment of booking the seat.​

/**
* Real-World Example 2: Movie Theater Booking
* Concept: Synchronized Blocks
*
* Scenario: A popular movie has only a few seats left. Multiple users are trying to book
* the last seat simultaneously.
* * Why Synchronized Block?
* We don't need to lock the entire method (which might include logging in, selecting dates, etc.).
* We only need to lock the specific logic that checks seat availability and books it.
* This improves performance.
*/
class TheaterBookingSystem {
int totalSeats = 10;
public void bookSeat(String user, int seatsRequested) {
System.out.println(user + " has entered the booking system.");
System.out.println(user + " is selecting seats...");
// ... heavy logic like rendering seat map (no sync needed here) ...
// Critical Section: Only this part needs to be thread-safe
synchronized (this) {
System.out.println(user + " is checking availability for " + seatsRequested + " seats.");

if (totalSeats >= seatsRequested) {
try {
// Simulate payment processing time
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println(e);
}

totalSeats = totalSeats - seatsRequested;
System.out.println("SUCCESS: " + user + " booked " + seatsRequested + " seats.");
System.out.println("Seats left: " + totalSeats);
} else {
System.out.println("FAILURE: " + user + " could not book. Not enough seats.");
}
}

System.out.println(user + " has left the booking system.\n");
}
}
class Customer extends Thread {
TheaterBookingSystem system;
String name;
int seats;
Customer(TheaterBookingSystem system, String name, int seats) {
this.system = system;
this.name = name;
this.seats = seats;
}
public void run() {
system.bookSeat(name, seats);
}
}
public class MovieTheaterSeats {
public static void main(String[] args) {
TheaterBookingSystem bms = new TheaterBookingSystem();
// 3 customers trying to book seats
Customer c1 = new Customer(bms, "Alice", 4);
Customer c2 = new Customer(bms, "Bob", 5);
Customer c3 = new Customer(bms, "Charlie", 5); // Should fail if Alice and Bob succeed
c1.start();
c2.start();
c3.start();
}
}

3. SharedPrinter.java: Focuses on Resource Locking. This demonstrates that synchronization isn't just about calculating numbers correctly; it's also about ensuring a process (like printing a multi-page document) finishes completely before another begins.

/**
* Real-World Example 3: Shared Office Printer
* Concept: Synchronization on a shared Resource (Object Locking)
*
* Scenario: Multiple employees send documents to a single shared printer.
* * Problem: If synchronization is missing, the printer might print Page 1 of Employee A,
* then Page 1 of Employee B, then Page 2 of Employee A. The documents get mixed up.
* * Solution: The printer object is locked. Until Employee A's full document is done,
* Employee B cannot start printing.
*/
class Printer {
// This method prints a document with multiple pages
// Using 'synchronized' ensures complete execution before another thread enters
synchronized void printDocument(String employeeName, String docName, int pages) {
System.out.println(">> Printer is now locked by " + employeeName);

for (int i = 1; i <= pages; i++) {
System.out.println("Printing " + docName + " - Page " + i + " for " + employeeName);
try {
Thread.sleep(400); // Time taken to print a page
} catch (Exception e) {
System.out.println(e);
}
}

System.out.println("<< " + employeeName + " finished printing. Printer released.\n");
}
}
class Employee extends Thread {
Printer printerRef;
String employeeName;
String docName;
int pages;
Employee(Printer p, String name, String doc, int pages) {
this.printerRef = p;
this.employeeName = name;
this.docName = doc;
this.pages = pages;
}
public void run() {
printerRef.printDocument(employeeName, docName, pages);
}
}
public class SharedPrinter {
public static void main(String[] args) {
// One single printer object shared among all employees
Printer officePrinter = new Printer();
Employee emp1 = new Employee(officePrinter, "John", "Quarterly_Report", 3);
Employee emp2 = new Employee(officePrinter, "Sarah", "Meeting_Minutes", 2);
Employee emp3 = new Employee(officePrinter, "Mike", "Resignation_Letter", 1);
// Even though they start at roughly the same time, the output will be orderly
emp1.start();
emp2.start();
emp3.start();
}
}


2. Thread Synchronization in Java


1. Scenario: An Online Store InventoryIn this real-world scenario, thousands of users might be browsing an item (e.g., a limited edition sneaker) simultaneously.

  • Browsing (Non-Critical): We don't want to lock the system just because someone is looking at the product page. This happens in parallel.

  • Buying (Critical): We only need to lock the system for the brief millisecond when the stock count is actually checked and decremented.

/**
* Real-World Example: Online Store Inventory
* Concept: Synchronized Block
* * Why use a Synchronized Block here?
* 1. Performance: We allow multiple customers to "browse" and "add to cart"
* at the same time without waiting (Parallel Execution).
* 2. Data Integrity: We only lock the specific lines of code that change the
* inventory count (Critical Section).
*/
class Inventory {
int stock = 5; // Limited stock available
public void purchaseItem(String customerName, int quantity) {
// --- 1. Non-Synchronized Zone (Parallel) ---
// Multiple threads can execute this part simultaneously.
// This represents browsing, logging validation, or adding to cart.
System.out.println(customerName + " is browsing and adding to cart...");

try {
Thread.sleep(1000); // Simulate time spent browsing
} catch (InterruptedException e) {
e.printStackTrace();
}
// --- 2. Synchronized Block (Serial/Locked) ---
// We only lock the Inventory object when we are ready to change the data.
synchronized (this) {
System.out.println(">> " + customerName + " entered the checkout (Critical Section).");

if (stock >= quantity) {
System.out.println("CONFIRMED: " + customerName + " purchased " + quantity + " item(s).");
stock = stock - quantity;
System.out.println("Stock remaining: " + stock);

// Simulate payment processing time
try { Thread.sleep(500); } catch (Exception e) {}

} else {
System.out.println("FAILED: " + customerName + " tried to buy, but Out of Stock.");
}

System.out.println("<< " + customerName + " left the checkout.\n");
}
}
}
class Shopper extends Thread {
Inventory inventoryRef;
String name;
int qty;
Shopper(Inventory inventory, String name, int qty) {
this.inventoryRef = inventory;
this.name = name;
this.qty = qty;
}
public void run() {
inventoryRef.purchaseItem(name, qty);
}
}
public class OnlineStore {
public static void main(String[] args) {
Inventory nikeStore = new Inventory();
// 3 Shoppers trying to buy the limited stock
Shopper s1 = new Shopper(nikeStore, "User_1", 2);
Shopper s2 = new Shopper(nikeStore, "User_2", 3);
Shopper s3 = new Shopper(nikeStore, "User_3", 2); // Should fail as stock is only 5
// You will see "browsing" happen for everyone first (Parallel),
// then "checkout" happens one by one (Synchronized).
s1.start();
s2.start();
s3.start();
}
}
​


​Key Takeaway from this code:If you run this, you will notice that all three users (User_1, User_2, User_3) print "is browsing..." almost instantly and simultaneously. They don't wait for each other there. They only wait when they see the message "entered the checkout". This is the power of the Synchronized Blockβ€”it maximizes efficiency.​


2. ParkingLotSystem.java: Notice how cars can "drive" and "scan tickets" in parallel (messages will mix), but the "Gate logic" prints sequentially.​

/**
* Real-World Example: Smart Parking Lot
* Concept: Synchronized Block
* * Scenario: Cars arrive at the parking gate simultaneously.
* 1. Parallel (Non-Synchronized): Driving to the entrance and scanning the ticket
* can happen for multiple cars at once (if there are multiple lanes).
* 2. Serial (Synchronized): The barrier logic that checks available spots and
* decrements the counter must be locked so two cars don't grab the last spot.
*/
class ParkingGarage {
int availableSlots = 3;
public void parkCar(String carName) {
// --- 1. Non-Synchronized Zone (Parallel) ---
System.out.println(carName + " is driving to the entrance gate...");

try {
Thread.sleep(500); // Simulate driving time
} catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(carName + " is scanning ticket...");
// --- 2. Synchronized Block (Critical Section) ---
synchronized (this) {
System.out.println(">> Gate logic processing for " + carName);

if (availableSlots > 0) {
System.out.println("APPROVED: " + carName + " parked successfully.");
availableSlots--;
System.out.println("Slots left: " + availableSlots);
} else {
System.out.println("DENIED: " + carName + " - Parking Full!");
}

System.out.println("<< " + carName + " logic finished.\n");
}
}
}
class Car extends Thread {
ParkingGarage garage;
String name;
Car(ParkingGarage g, String n) {
this.garage = g;
this.name = n;
}
public void run() {
garage.parkCar(name);
}
}
public class ParkingLotSystem {
public static void main(String[] args) {
ParkingGarage mallParking = new ParkingGarage();
// 5 cars trying to park in 3 spots
Car c1 = new Car(mallParking, "Ford");
Car c2 = new Car(mallParking, "Toyota");
Car c3 = new Car(mallParking, "Honda");
Car c4 = new Car(mallParking, "BMW");
Car c5 = new Car(mallParking, "Tesla");
c1.start();
c2.start();
c3.start();
c4.start();
c5.start();
}
}

3. VotingSystem.java: Similar concept, where the "thinking time" is parallel, but the totalVotes increment is safe inside the synchronized block.​

/**
* Real-World Example: Electronic Voting Booth
* Concept: Synchronized Block
* * Scenario: Multiple voters enter different booths at a polling station.
* 1. Parallel: Reading instructions and thinking about who to vote for.
* 2. Serial: The exact moment the vote is cast, it updates a central
* 'totalVotes' counter. This specific update must be synchronized.
*/
class VoteCounter {
int totalVotes = 0;
public void castVote(String voterName) {
// --- 1. Non-Synchronized Zone (Parallel) ---
// Voters are inside the booth thinking. This doesn't need to block others.
System.out.println(voterName + " has entered the booth and is reviewing candidates...");

try {
Thread.sleep(1000); // Simulate decision making time
} catch (InterruptedException e) { e.printStackTrace(); }
// --- 2. Synchronized Block (Critical Section) ---
// Only one vote can be added to the official counter at a time to prevent data loss.
synchronized (this) {
System.out.println(">> " + voterName + " is pressing the VOTE button.");

totalVotes++;
System.out.println("Vote Cast! Total Votes: " + totalVotes);

System.out.println("<< " + voterName + " left the booth.\n");
}
}
}
class Voter extends Thread {
VoteCounter counterRef;
String name;
Voter(VoteCounter counter, String name) {
this.counterRef = counter;
this.name = name;
}
public void run() {
counterRef.castVote(name);
}
}
public class VotingSystem {
public static void main(String[] args) {
VoteCounter centralCounter = new VoteCounter();
// 4 Voters voting simultaneously
Voter v1 = new Voter(centralCounter, "Voter_Alice");
Voter v2 = new Voter(centralCounter, "Voter_Bob");
Voter v3 = new Voter(centralCounter, "Voter_Charlie");
Voter v4 = new Voter(centralCounter, "Voter_Dave");
v1.start();
v2.start();
v3.start();
v4.start();
}
}

These examples reinforce the pattern: do the heavy lifting outside the lock, and only lock when you touch shared data.

← Back to Learning Journey