Java Academy Logo

Java Academy

Synchronization in Java

Learn how to keep shared data safe when many threads run at the same time — and how to do it without slowing your program.

Part 1

What is Synchronization?

Synchronization means: only one thread can use a shared piece of code or data at a time.

Why do we need it?

When two threads change the same data at the same time, the result can be wrong. This is called a race condition.

int balance = 100;

// Thread A: withdraw 60
// Thread B: withdraw 60
// Both may think balance is still 100
// Final balance can become wrong

Synchronization fixes this by letting only one thread enter the important code at a time.

Simple idea

Think of a bathroom with a lock on the door:

  • One person goes in and locks the door
  • Others wait outside
  • When the person leaves, the next person can enter

In Java, synchronized is that lock.

Part 2

How to Use synchronized

Java gives you two common ways: a synchronized method, or a synchronized block.

1. Synchronized method

Put synchronized on a method. Only one thread can run that method on the same object at a time.

class BankAccount {
    private int balance = 0;

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

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

Good when the whole method must be protected.

2. Synchronized block (better for performance)

Lock only the small part that needs protection. Leave the rest of the method free.

class OrderService {
    private final Object lock = new Object();
    private int orderCount = 0;

    public void placeOrder(String item) {
        // This part does NOT need a lock
        System.out.println("Preparing order for " + item);

        // Only this part is locked
        synchronized (lock) {
            orderCount++;
        }

        // This part is free again
        System.out.println("Order ready");
    }
}

This is usually better than locking a whole method, because other threads can still do the work that does not touch shared data.

3. Static synchronization

Normal synchronized locks one object. Static synchronization locks the whole class, because static data is shared by every object of that class.

Normal synchronized

Protects data that belongs to one object. Example: one bank account's balance.

Static synchronized

Protects data shared by all objects. Example: a shared ID counter for the whole app.

Example: every new user needs a unique ID. That counter is static, so all threads must take turns updating it.

class IdGenerator {
    private static int nextId = 1;

    // Only one thread can run this at a time
    // for the whole IdGenerator class
    public static synchronized int next() {
        return nextId++;
    }
}

You can also write it like this:

public static int next() {
    synchronized (IdGenerator.class) {
        return nextId++;
    }
}

Simple rule: use static synchronization when the shared value belongs to the class (static), not to one object.

synchronized method vs block

StyleMeaningBest when
synchronized methodLocks the whole methodMethod is short and all of it needs safety
synchronized blockLocks only a small partYou want better speed and less waiting

Part 3

Use Synchronization Without Performance Issues

Synchronization is useful, but too much locking can make your app slow. Follow these rules.

Rule 1: Keep the locked part small

Only lock the lines that touch shared data. Do not put long work inside synchronized.

Slow (bad)

synchronized (lock) {
    // network call
    // file write
    // heavy loop
    balance += amount;
}

Faster (good)

// do slow work outside
String result = callApi();

synchronized (lock) {
    balance += amount;
}

Other threads should not wait while one thread does network calls, file writes, or long calculations.

Rule 2: Prefer synchronized blocks over big methods

If a method has 50 lines, but only 2 lines need a lock, do not make the whole method synchronized. Use a synchronized block for those 2 lines.

Rule 3: Do not use one lock for everything

If you lock the whole object for every action, threads wait too often. Use separate locks for separate data when it is safe.

class Store {
    private final Object ordersLock = new Object();
    private final Object usersLock = new Object();

    private int orders = 0;
    private int users = 0;

    public void addOrder() {
        synchronized (ordersLock) {
            orders++;
        }
    }

    public void addUser() {
        synchronized (usersLock) {
            users++;
        }
    }
}

Now one thread can add an order while another thread adds a user. They do not block each other.

Rule 4: Avoid nested locks (deadlock risk)

If Thread A locks Lock1 and waits for Lock2, while Thread B locks Lock2 and waits for Lock1, both wait forever. This is a deadlock.

Tip: if you must use two locks, always take them in the same order in every place.

Rule 5: Prefer Atomic or concurrent tools when possible

For simple counters, you may not need synchronized at all. Use atomic classes instead.

import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger visits = new AtomicInteger(0);
visits.incrementAndGet(); // safe and often faster for simple counts

For maps and lists shared by many threads, prefer:

  • ConcurrentHashMap instead of synchronizing a normal HashMap
  • CopyOnWriteArrayList when reads are many and writes are few

Learn more in Atomic Variables and Multithreading.

Rule 6: Do not synchronize on public objects

Avoid synchronized (this) when outside code can also lock on the same object. Prefer a private lock object.

// Better
private final Object lock = new Object();

synchronized (lock) {
    // critical code
}

Quick checklist for good performance

  • Lock only shared data, not the whole method if possible
  • Keep synchronized blocks short and fast
  • Never do I/O or slow calls inside a lock
  • Use separate locks for unrelated data
  • Prefer AtomicInteger / concurrent collections for simple cases
  • Avoid nested locks when you can

Remember: Synchronization should protect data. It should not become a long waiting line for every thread.

Part 4

FAQ

Is synchronized always slow?

No. A short synchronized block is usually fine. It becomes slow when the locked part is long, or when too many threads wait on the same lock.

Should I make every method synchronized?

No. Only protect the code that changes shared data. Extra locks create extra waiting.

When should I use AtomicInteger instead?

Use AtomicInteger for simple counters and flags. Use synchronized when you need to update more than one related field together.

What is the safest beginner pattern?

Use a private final Object lock, put only a few lines inside synchronized (lock), and keep slow work outside the lock.

Continue Learning