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:

  1. The Lock Acquisition: When Thread A calls increment(), it looks at the Counter object. Every object in Java has a Monitor. Thread A “grabs” the monitor.
  2. The Exclusion: While Thread A is inside increment(), Thread B tries to call increment(). The JVM sees that Thread A holds the monitor. Thread B is put into a BLOCKED state (it stops executing and waits).
  3. 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.
  4. 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 volatile keyword helps with “Visibility” (seeing the latest value), but it does not provide “Atomicity” (protecting the Read-Modify-Write cycle). You still need synchronized for count++.