In this tutorial, you’ll learn everything about Synchronization in Java — what it is, why it’s needed, and how it ensures thread safety when multiple threads access shared resources.

We’ll also explore different types of synchronization (method, block, static) and see how Java handles thread communication internally.


🔹 What is Synchronization?

Synchronization is a process that allows only one thread to access a shared resource (like a variable, method, or object) at a time. It helps prevent issues like race conditions and ensures data consistency.

In simple terms — synchronization means giving one thread at a time the “key” to access a shared resource while others wait for their turn.


🔹 Why Synchronization is Needed

Let’s consider a simple example where multiple threads increment a shared counter without synchronization:


class Counter {
    int count = 0;

    public void increment() {
        count++;
    }
}

public class WithoutSyncExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final Count: " + counter.count);
    }
}

Expected Output: 2000
Actual Output: may vary (like 1945, 1990, etc.) due to race conditions.


🔹 How Synchronization Solves This

When we add the synchronized keyword to the method, Java ensures only one thread can execute it at a time.


class Counter {
    int count = 0;

    public synchronized void increment() {
        count++;
    }
}

Now both threads will wait for each other and the final output will always be 2000 ✅.


🔹 Types of Synchronization in Java

There are mainly three types of synchronization:

  1. Method Synchronization
  2. Block Synchronization
  3. Static Synchronization

1️⃣ Method Synchronization

When a method is declared with synchronized, only one thread can execute that method on the same object at a time.


class Display {
    public synchronized void show(String name) {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Good morning: " + name);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class MethodSyncExample {
    public static void main(String[] args) {
        Display display = new Display();

        Thread t1 = new Thread(() -> display.show("Alice"));
        Thread t2 = new Thread(() -> display.show("Bob"));

        t1.start();
        t2.start();
    }
}

Output:
All “Good morning: Alice” messages print first, then all “Good morning: Bob” messages — ensuring thread-safe execution.


2️⃣ Block Synchronization

Instead of synchronizing the whole method, you can synchronize only a small block of code. This improves performance.


class Display {
    public void show(String name) {
        synchronized(this) { // Lock on current object
            for (int i = 1; i <= 3; i++) {
                System.out.println("Good morning: " + name);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

This ensures only the critical section (shared resource) is locked, allowing better concurrency.


3️⃣ Static Synchronization

When multiple threads access a static method, synchronization is done on the class-level lock, not the object-level lock.


class Display {
    public static synchronized void show(String name) {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Good morning: " + name);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class StaticSyncExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> Display.show("Alice"));
        Thread t2 = new Thread(() -> Display.show("Bob"));

        t1.start();
        t2.start();
    }
}

Here, even though there’s no object, synchronization works using the class lock — so only one thread executes at a time.


🔹 Object Lock vs Class Lock

  • Object Lock: Acquired when synchronizing a non-static method or block (specific instance).
  • Class Lock: Acquired when synchronizing a static method or block (applies to entire class).

🔹 Synchronization Example with Multiple Objects


class Display {
    public synchronized void show(String name) {
        for (int i = 1; i <= 3; i++) {
            System.out.println("Good morning: " + name);
        }
    }
}

public class MultiObjectExample {
    public static void main(String[] args) {
        Display d1 = new Display();
        Display d2 = new Display();

        Thread t1 = new Thread(() -> d1.show("Alice"));
        Thread t2 = new Thread(() -> d2.show("Bob"));

        t1.start();
        t2.start();
    }
}

Output:
The outputs may interleave because each object has its own lock. Synchronization applies per object instance.


🔹 Real-World Example

In a banking system, you don’t want two threads to withdraw money from the same account simultaneously.


class Account {
    private int balance = 1000;

    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            System.out.println(Thread.currentThread().getName() + " is withdrawing " + amount);
            balance -= amount;
            System.out.println("Remaining Balance: " + balance);
        } else {
            System.out.println("Insufficient balance for " + Thread.currentThread().getName());
        }
    }
}

public class BankExample {
    public static void main(String[] args) {
        Account account = new Account();

        Thread t1 = new Thread(() -> account.withdraw(800), "Thread-1");
        Thread t2 = new Thread(() -> account.withdraw(800), "Thread-2");

        t1.start();
        t2.start();
    }
}

Output:
Only one thread successfully withdraws money; the second gets “Insufficient balance”. ✅


🔹 Advantages of Synchronization

  • Ensures thread safety
  • Prevents race conditions
  • Maintains data consistency

🔹 Disadvantages of Synchronization

  • Performance overhead (threads wait for each other)
  • Can lead to deadlocks if locks are not managed properly
  • Reduced concurrency

🔹 Interview Questions

  1. What is synchronization and why is it needed?
  2. Difference between synchronized method and synchronized block?
  3. What’s the difference between object lock and class lock?
  4. Can you synchronize a constructor?
  5. What happens if a thread throws an exception inside a synchronized block?

Answer: The lock is automatically released when an exception occurs or when the method completes.


🧠 Summary

  • Synchronization controls thread access to shared resources.
  • Use synchronized methods or blocks to prevent race conditions.
  • Always keep synchronized blocks small for better performance.
  • Be careful of deadlocks when using multiple locks.

✅ Conclusion

Synchronization in Java is essential when working with multithreading. It ensures that shared resources are accessed safely and consistently. However, use it wisely — excessive synchronization can reduce performance and cause thread contention.