Real World Example

IRCTC-style Seat Booking

When thousands of users try to book the last seats on a train at 10 AM tatkal time, the seat count must never go below zero. Each booking is a check-then-act operation that must be atomic.

Example

8 threads (users) try to book 3 seats each from a coach with 20 seats. With synchronized, exactly 20 seats are sold and no seat is double-booked.

import java.util.*;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Coach coach = new Coach(20);
        int[] booked = new int[8];
        List<Thread> users = new ArrayList<>();
        for (int u = 0; u < 8; u++) {
            final int user = u;
            Thread t = new Thread(() -> {
                for (int i = 0; i < 3; i++) if (coach.bookSeat()) booked[user]++;
            });
            users.add(t);
            t.start();
        }
        for (Thread t : users) t.join();

        int totalBooked = Arrays.stream(booked).sum();
        System.out.println("Seats booked: " + totalBooked);
        System.out.println("Seats left:   " + coach.available());
        System.out.println("Oversold?     " + (totalBooked > 20));
    }
}

class Coach {
    private int available;
    Coach(int seats) { this.available = seats; }

    synchronized boolean bookSeat() {
        if (available > 0) {          // check
            available--;              // act — atomic together under the lock
            return true;
        }
        return false;
    }
    synchronized int available() { return available; }
}

Output

Seats booked: 20
Seats left:   0
Oversold?     false

In Production

A single JVM lock only protects one server. Real booking systems run many instances, so they also use the database for safety: UPDATE seats SET available = available - 1 WHERE train_id = ? AND available > 0 (atomic in the DB), optimistic locking with a @Version column, or a distributed lock in Redis.