1. The “Why”
We use synchronization to prevent Data Corruption. Without it, two threads can “read” the same initial value, perform an operation, and “write” back their results, effectively overwriting each other. Synchronization forces these operations to happen one after the other (Sequentially) rather than at the same time (Concurrently).
2. Detailed Code Explanation
Let’s look at a common pattern: The Thread-Safe Counter.
public class Counter {
private int count = 0;
// The 'synchronized' keyword tells Java:
// "Only one thread can enter this method at a time using this specific object's lock."
public synchronized void increment() {
// Step 1: Read 'count' from Memory
// Step 2: Add 1
// Step 3: Write 'count' back to Memory
this.count++;
}
public synchronized int getCount() {
return this.count;
}
}
How the JVM executes this:
- The Lock Acquisition: When Thread A calls
increment(), it looks at theCounterobject. Every object in Java has a Monitor. Thread A “grabs” the monitor. - The Exclusion: While Thread A is inside
increment(), Thread B tries to callincrement(). The JVM sees that Thread A holds the monitor. Thread B is put into a BLOCKED state (it stops executing and waits). - The Memory Barrier: When Thread A finishes, it “releases” the monitor. Crucially, it also flushes its changes to the Main Memory (Heap) so other threads can see the updated value.
- The Hand-off: The JVM wakes up Thread B. Thread B now acquires the monitor and sees the updated value left by Thread A.
3. Finer-Grained Synchronization (The Block)
Sometimes, synchronizing an entire method is overkill. If your method is 100 lines long, but only 1 line touches the shared variable, you should use a Synchronized Block.
public class BetterCounter {
private int count = 0;
private final Object lock = new Object(); // A dedicated "lock" object
public void performTask() {
// PHASE 1: This part can be done by 100 threads at once (Parallel)
String threadName = Thread.currentThread().getName();
System.out.println("Processing data for: " + threadName);
// PHASE 2: This is the Critical Section (Sequential)
synchronized (lock) {
count++;
}
// PHASE 3: Back to Parallel
System.out.println("Task complete for: " + threadName);
}
}
4. Comparison Table: Method vs. Block
| Feature | synchronized Method |
synchronized Block |
|---|---|---|
| Scope | Entire method body. | Only the code inside { }. |
| Lock Object | Uses this (the current instance). |
You choose the object (e.g., lock). |
| Performance | Slower (locks the object longer). | Faster (minimal locking time). |
| Flexibility | Low (always locks on this). |
High (can use multiple locks for different variables). |
5. The Gotchas
- Deadlocks: If Thread 1 is waiting for a lock held by Thread 2, and Thread 2 is waiting for a lock held by Thread 1, the program freezes forever.
- Primitive Variables: You cannot synchronize on a primitive (e.g.,
synchronized(count)will fail). You must synchronize on an Object. - Volatile is NOT Synchronized: The
volatilekeyword helps with “Visibility” (seeing the latest value), but it does not provide “Atomicity” (protecting the Read-Modify-Write cycle). You still needsynchronizedforcount++.