Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
82: The synchronized Keyword
Imagine you're at a busy coffee shop that has exactly one single-occupancy restroom. To keep things orderly, there is a lock on the door. When a customer goes in, they lock the door behind them. If another customer tries to enter, they find the door locked and simply wait in line. Once the first person leaves and unlocks the door, the next person in line can step in and lock it for themselves.
In Java, the synchronized keyword is that lock. Without it, you have what we call a "race condition," which is basically the software equivalent of three people trying to use the same restroom at the exact same time. It's messy, it's chaotic, and in a production environment, it leads to data corruption that is a nightmare to debug because it only happens occasionally.
The Chaos of Concurrent Access
Let's look at a real scenario. Suppose we're building a simple bank account system. We have a balance, and we have a method to withdraw money. If only one thread is running, everything is fine. But if two threads (representing two different ATMs) try to withdraw money from the same account at the exact same millisecond, we run into trouble.
public class BankAccount {
private int balance = 100;
public void withdraw(int amount) {
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " is about to withdraw...");
balance -= amount;
System.out.println(Thread.currentThread().getName() + " finished. Balance: " + balance);
} else {
System.out.println("Insufficient funds for " + Thread.currentThread().getName());
}
}
}
Here is the danger: Thread A checks the balance and sees $100. Before it can actually subtract the money, the CPU switches to Thread B. Thread B also sees $100. Both threads think they have enough money, both subtract $80, and suddenly your balance is -$60. The bank is unhappy, and your code is broken.
Putting a Lock on the Door
To fix this, we use the synchronized keyword. When you mark a method as synchronized, Java ensures that only one thread can execute that method on a specific instance of the object at a time. It's like the restroom lock—the first thread to enter "grabs the lock," and all other threads must wait until that method finishes.
public synchronized void withdraw(int amount) {
if (balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " withdrew " + amount);
}
}
Now, if Thread A enters withdraw(), it locks the entire BankAccount object. If Thread B tries to call withdraw() on that same object, it's blocked. It stays in a "Blocked" state until Thread A exits the method and releases the lock. I've spent way too many hours of my career chasing bugs that were caused by forgetting this one keyword.
Precision Locking with Synchronized Blocks
Now, here is a pro tip: synchronizing an entire method is easy, but it can be slow. If your method does a lot of work (like calling a slow web service) and only one small part of it actually touches the shared data, you're locking the door for way longer than you need to. This kills your application's performance.
Instead, you can use a synchronized block. This allows you to lock only the specific lines of code that are "critical." You have to tell Java which object to use as the lock (usually this).
public void withdraw(int amount) {
// Non-critical code here (e.g., logging, validation)
System.out.println("Processing request for " + Thread.currentThread().getName());
synchronized(this) {
// Only this part is locked
if (balance >= amount) {
balance -= amount;
}
}
// More non-critical code here
System.out.println("Transaction complete.");
}
By narrowing the scope, you let other threads do the "non-critical" work in parallel, only making them wait for the actual balance update. It's the difference between locking the entire coffee shop or just locking the restroom door.
📋 Practical Task
Fixing the Double-Booked Concert Seat
You are building a ticket booking system. Currently, the system has a bug where two users can book the same last remaining seat because the bookSeat() method isn't thread-safe. Your task is to implement synchronization to ensure that only one person can successfully book the final seat.
public class TicketSystem {
private int availableSeats = 1;
public void bookSeat(String customerName) {
if (availableSeats > 0) {
System.out.println(customerName + " is booking the seat...");
try {
// Simulating a slight delay in processing
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
availableSeats--;
System.out.println("Seat booked successfully for: " + customerName);
} else {
System.out.println("Sorry " + customerName + ", the seats are sold out!");
}
}
public static void main(String[] args) {
TicketSystem system = new TicketSystem();
// Two people trying to book the same last seat simultaneously
Thread user1 = new Thread(() -> system.bookSeat("Alice"), "User-1");
Thread user2 = new Thread(() -> system.bookSeat("Bob"), "User-2");
user1.start();
user2.start();
}
}
Goal: Modify the TicketSystem class so that only one user (either Alice or Bob) gets the seat, and the other receives the "sold out" message. You can use either a synchronized method or a synchronized block.
There are no comments for now.