Java Synchronization

Java synchronization is a way to control how multiple threads access shared data. It helps prevent two threads from changing the same object at the same time in a way that produces incorrect results.

Why Synchronization Is Needed

When threads share mutable data, timing matters. An operation such as balance++ or balance = balance + amount may look like one step, but it is really several steps: read the value, calculate a new value, and store it. If two threads interleave those steps, one update can overwrite another.

The synchronized keyword protects a section of code with a lock. Only one thread at a time can hold the same lock. Other threads that need that lock must wait until it is released.

Synchronized Methods

A synchronized instance method uses the current object as its lock. In the next example, both threads share the same BankAccount object. The deposit method is synchronized, so only one thread can update the balance on that account at a time.

public class Main {
    static class BankAccount {
        private int balance = 0;

        public synchronized void deposit(int amount) {
            balance += amount;
        }

        public synchronized int getBalance() {
            return balance;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BankAccount account = new BankAccount();

        Thread first = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                account.deposit(1);
            }
        });

        Thread second = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                account.deposit(1);
            }
        });

        first.start();
        second.start();
        first.join();
        second.join();

        System.out.println("Balance: " + account.getBalance());
    }
}

Output:

Balance: 2000

How the Example Works

The two threads each call deposit(1) one thousand times. Because deposit is synchronized, one thread must finish each deposit before another thread can run the same method on the same account object. The join calls make the main thread wait until both worker threads are done before printing the balance.

The getBalance method is also synchronized. That makes reading the shared value consistent with the synchronized writes.

Synchronized Blocks

You can also synchronize only part of a method. A synchronized block is useful when a method does some work that does not need locking and only a smaller section touches shared data.

public class Main {
    static class Logger {
        private final Object lock = new Object();
        private int messages = 0;

        public void logMessage(String text) {
            String formatted = "Logged: " + text;

            synchronized (lock) {
                messages++;
                System.out.println(formatted + " (#" + messages + ")");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Logger logger = new Logger();

        Thread first = new Thread(() -> logger.logMessage("alpha"));
        first.start();
        first.join();

        Thread second = new Thread(() -> logger.logMessage("beta"));
        second.start();
        second.join();
    }
}

Output:

Logged: alpha (#1)
Logged: beta (#2)

Choosing a Lock

A synchronized method locks on this for an instance method. A synchronized block can lock on any object you choose. Many classes use a private final lock object, like private final Object lock = new Object();, so outside code cannot accidentally use the same lock.

For static synchronized methods, the lock belongs to the class object, not to one instance. This protects static shared data that belongs to the class as a whole.

Good Habits

  • Synchronize every access that reads or writes the same shared mutable data.
  • Keep synchronized sections small and focused.
  • Do not call slow or unknown external code while holding a lock unless you have a clear reason.
  • Use higher-level tools such as java.util.concurrent classes when they fit the problem.

Takeaway: synchronization makes shared data safer by allowing only one thread at a time to run protected code guarded by the same lock.