Question

What `synchronized` Means in Java: Thread Safety Explained

javamultithreadingkeywordsynchronized

Question

I have several questions about the synchronized keyword in Java.

  • What is the purpose of the synchronized keyword?
  • When should a method or block be synchronized?
  • What does synchronized mean both programmatically and logically in multithreaded code?

Short Answer

By the end of this page, you will understand what synchronized does in Java, why it matters in multithreaded programs, when to use it, and how it prevents multiple threads from changing shared data at the same time. You will also see practical examples, common mistakes, and how developers use synchronization in real codebases.

Concept

synchronized is a Java keyword used to control access to shared data when multiple threads are running at the same time.

In a multithreaded program, two or more threads may try to read or modify the same object or variable. If this happens without coordination, the program can produce incorrect results. This kind of problem is called a race condition.

synchronized helps by allowing only one thread at a time to execute a specific critical section of code for a given lock.

What it does

When a thread enters synchronized code, it must first acquire a monitor lock.

  • If the lock is free, the thread enters the block or method.
  • If another thread already holds the lock, the current thread waits.
  • When the first thread leaves the synchronized code, it releases the lock.

Why this matters

Without synchronization, operations that look simple may actually happen in multiple steps.

For example, this is not atomic:

count++;

It is roughly:

  1. Read count
  2. Add 1
  3. Write the new value back

If two threads do this at the same time, one update can be lost.

Programmatically

synchronized means:

  • acquire a lock before entering the protected code
  • allow one thread at a time for that lock
  • release the lock when done
  • guarantee visibility of changes made inside the synchronized section to other threads that later acquire the same lock

Logically

It means:

  • this code uses shared state
  • that shared state must not be modified concurrently without coordination
  • the protected code is a critical section

Method vs block synchronization

You can synchronize:

  • an instance method: locks on this
  • a static method: locks on the Class object
  • a block: locks on a specific object

This lets you control how much code is protected and which threads must wait for each other.

Mental Model

Imagine a shared bathroom with one key.

  • The bathroom is the shared resource.
  • The key is the lock.
  • A person is a thread.

If someone has the key, no one else can enter. Others must wait until the key is returned.

That is what synchronized does: it makes threads take turns when using shared data.

A second way to think about it is a notebook on a desk shared by multiple people.

  • If everyone writes at the same time, the notes become corrupted.
  • If only one person can use the notebook at a time, the data stays consistent.

So synchronized is Java's way of saying: only one thread may work with this shared state at a time.

Take Quiz

Syntax and Examples

Synchronized instance method

class Counter {
    private int count = 0;

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

    public synchronized int getCount() {
        return count;
    }
}

Here:

  • both methods lock on the current object (this)
  • if one thread is inside increment(), another thread cannot enter increment() or getCount() on the same object at the same time

Synchronized block

class Counter {
    private int count = 0;
    private final Object lock = new Object();

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

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

This version uses a separate lock object.

Why do this?

  • it gives more control
  • it avoids locking the whole object publicly
  • it protects only the critical code section

Static synchronized method

class IdGenerator {
    private static int nextId = 1;

    public static synchronized int generateId() {
        return nextId++;
    }
}

This locks on the IdGenerator.class object, not on an instance.

Beginner-friendly example

class BankAccount {
    private int balance = 100;

    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }

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

If two threads try to withdraw money at the same time, synchronization helps prevent inconsistent updates.

Important note

synchronized is only useful when:

  • multiple threads are involved, and
  • they share mutable state

If data is local to one thread, synchronization is usually unnecessary.

Step by Step Execution

Consider this example:

class Counter {
    private int count = 0;

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

Suppose two threads, Thread-A and Thread-B, both call increment() on the same Counter object.

Step-by-step

  1. Thread-A tries to enter increment().
  2. Because the method is synchronized, Java checks whether the object's lock is available.
  3. The lock is free, so Thread-A acquires it.
  4. Thread-A executes count++.
  5. While Thread-A is still inside the method, Thread-B tries to enter increment().
  6. Thread-B cannot enter because Thread-A holds the lock.
  7. Thread-B waits.
  8. Thread-A finishes the method and releases the lock.
  9. Now Thread-B acquires the lock.
  10. Thread-B executes count++.
  11. Thread-B leaves the method and releases the lock.

Result

The increments happen one at a time, not simultaneously.

Without synchronization

If the method were not synchronized, this could happen:

  • Thread-A reads count as 0
  • Thread-B reads count as 0
  • both compute 1
  • both write back 1

Expected result: 2

Actual result: 1

That is a race condition caused by unsynchronized access to shared data.

Real World Use Cases

synchronized is used when multiple threads access the same mutable data.

Common practical uses

  • Counters and metrics

    • tracking requests processed
    • counting tasks completed
  • Banking or wallet logic

    • deposits
    • withdrawals
    • balance updates
  • Shared in-memory caches

    • adding or updating values safely
  • Producer-consumer systems

    • coordinating access to shared queues or buffers
  • Configuration reload or state updates

    • preventing partial updates to shared settings
  • Legacy Java server code

    • protecting mutable singleton objects used by multiple request threads

Example scenario

A web server may handle many requests at once. If all requests update the same shared object, unsynchronized access can corrupt its state. Synchronization ensures that state changes happen safely.

Take Quiz

Real Codebase Usage

In real projects, developers rarely mark everything as synchronized. Instead, they apply it carefully around shared mutable state.

Common patterns

Guarding a critical section

synchronized (lock) {
    sharedMap.put(key, value);
}

Only the part that touches shared data is locked.

Guard clauses inside synchronized code

public synchronized void withdraw(int amount) {
    if (amount <= 0) {
        return;
    }

    if (balance < amount) {
        return;
    }

    balance -= amount;
}

This keeps business rules clear while still protecting shared state.

Private lock object

private final Object lock = new Object();

Many developers prefer a private lock instead of synchronized(this) because outside code cannot accidentally lock on it.

Synchronizing compound actions

A single put() on a thread-safe structure may be safe, but a multi-step operation often still needs locking.

synchronized (lock) {
    if (!map.containsKey(key)) {
        map.put(key, value);
    }
}

Error handling and state consistency

Synchronization is often used so that a state change either completes consistently or not at all.

In modern codebases

Developers may also use:

  • java.util.concurrent classes
  • ReentrantLock
  • atomic classes like AtomicInteger
  • immutable objects

But understanding synchronized is still essential because:

  • it is built into Java
  • it appears in many existing codebases
  • it teaches the basics of thread safety and locking

Common Mistakes

1. Synchronizing when there is no shared state

If a variable is local to a method, each thread gets its own copy.

public void doWork() {
    int x = 0; // local variable, not shared
    x++;
}

This does not need synchronization.

2. Locking the wrong object

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

This is broken because each call creates a new lock object. Threads are not actually coordinating with each other.

Use a shared lock:

private final Object lock = new Object();

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

3. Assuming synchronized makes everything thread-safe

It only protects code that uses the same lock consistently.

If one method uses synchronized(this) and another uses a different lock, they may not protect each other.

4. Synchronizing too much code

public synchronized void process() {
    readInput();
    doSlowNetworkCall();
    updateSharedState();
}

This can reduce performance because the lock is held longer than necessary.

Better:

public void process() {
    readInput();
    doSlowNetworkCall();

    synchronized (lock) {
        updateSharedState();
    }
}

5. Forgetting that static and instance locks are different

public synchronized void instanceMethod() {}
public static synchronized void staticMethod() {}

These do not use the same lock.

  • instance method locks on this
  • static method locks on ClassName.class

6. Ignoring deadlock risk

If two threads acquire locks in different orders, both can wait forever.

synchronized (lockA) {
    synchronized (lockB) {
        // work
    }
}

If another thread does lockB then lockA, deadlock is possible.

7. Using synchronized for simple counters when better tools exist

For high-concurrency counters, AtomicInteger is often simpler and faster than full method synchronization.

Comparisons

ConceptWhat it doesBest forNotes
synchronized methodLocks the whole methodSimple object-level protectionEasy to read, less flexible
synchronized blockLocks a specific sectionFine-grained lockingUsually preferred for control
volatileEnsures visibility of a variableFlags and simple state sharingDoes not make compound actions atomic
AtomicIntegerAtomic numeric operationsCounters, incrementsGood alternative to synchronized counters
ReentrantLockExplicit lock APIAdvanced lock controlSupports features like tryLock
Immutable objectsAvoid shared mutationSafer designOften removes need for locking

synchronized vs volatile

Use synchronized when you need:

  • mutual exclusion
  • atomic multi-step operations
  • coordinated access to shared mutable state

Use volatile when you need:

  • visibility of the latest value
  • simple reads/writes of a single variable

synchronized vs atomic classes

For this:

count++;

an AtomicInteger can be cleaner:

import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }
}

But for multiple related fields or business rules, synchronized is often easier to reason about.

Take Quiz

Cheat Sheet

Quick reference

Synchronized instance method

public synchronized void method() {
    // locks on this
}

Synchronized static method

public static synchronized void method() {
    // locks on ClassName.class
}

Synchronized block

synchronized (lockObject) {
    // critical section
}

Key rules

  • Use it only when threads share mutable data.
  • Only one thread at a time can hold a given lock.
  • Threads must use the same lock to coordinate properly.
  • synchronized provides both:
    • mutual exclusion
    • visibility of changes

Good candidates for synchronization

  • updating shared counters
  • modifying shared collections
  • read-modify-write operations
  • business rules involving shared state

Avoid when

  • data is local to one method
  • objects are immutable
  • thread-safe utilities already solve the problem

Edge cases

  • synchronized(this) can be risky if outside code also locks the same object.
  • static synchronized and instance synchronized do not share a lock.
  • locking on a new object each time does not work.
  • too much locking can hurt performance.
  • inconsistent lock ordering can cause deadlock.

FAQ

What does synchronized mean in Java?

It means only one thread at a time can execute a protected block or method for a specific lock.

When should I use synchronized?

Use it when multiple threads access the same mutable data and at least one thread writes to that data.

Does synchronized make code faster?

Not usually. Its main purpose is correctness, not speed. In some cases it can reduce performance because threads must wait.

Is synchronized the same as volatile?

No. volatile ensures visibility of a variable's latest value, but it does not protect multi-step operations like count++.

Should I synchronize every method?

No. Synchronize only the parts that access shared mutable state. Overusing it can make code slower and harder to maintain.

What object gets locked in a synchronized method?

For an instance method, Java locks this. For a static synchronized method, Java locks the class object.

Can synchronized prevent race conditions?

Yes, if all threads use the same lock consistently when accessing the shared state.

Is synchronized still used in modern Java?

Yes. Even though newer concurrency tools exist, synchronized is still common and important to understand.

Take Quiz

Mini Project

Description

Build a small Java program that simulates a shared bank account accessed by multiple threads. This project demonstrates why synchronized matters when several threads update the same data at the same time.

Goal

Create a thread-safe bank account where concurrent deposits and withdrawals do not corrupt the balance.

Requirements

  • Create a BankAccount class with a shared balance field.
  • Add deposit and withdraw methods that safely modify the balance.
  • Start multiple threads that perform deposits and withdrawals on the same account.
  • Print the final balance after all threads finish.
  • Ensure the final balance is consistent and not affected by race conditions.
Take Quiz

Keep learning

Related questions

View more