Question
What `synchronized` Means in Java: Thread Safety Explained
Question
I have several questions about the synchronized keyword in Java.
- What is the purpose of the
synchronizedkeyword? - When should a method or block be synchronized?
- What does
synchronizedmean 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:
- Read
count - Add 1
- 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
Classobject - 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.
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 enterincrement()orgetCount()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
Thread-Atries to enterincrement().- Because the method is
synchronized, Java checks whether the object's lock is available. - The lock is free, so
Thread-Aacquires it. Thread-Aexecutescount++.- While
Thread-Ais still inside the method,Thread-Btries to enterincrement(). Thread-Bcannot enter becauseThread-Aholds the lock.Thread-Bwaits.Thread-Afinishes the method and releases the lock.- Now
Thread-Bacquires the lock. Thread-Bexecutescount++.Thread-Bleaves 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-Areadscountas0Thread-Breadscountas0- 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.
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.concurrentclassesReentrantLock- 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
| Concept | What it does | Best for | Notes |
|---|---|---|---|
synchronized method | Locks the whole method | Simple object-level protection | Easy to read, less flexible |
synchronized block | Locks a specific section | Fine-grained locking | Usually preferred for control |
volatile | Ensures visibility of a variable | Flags and simple state sharing | Does not make compound actions atomic |
AtomicInteger | Atomic numeric operations | Counters, increments | Good alternative to synchronized counters |
ReentrantLock | Explicit lock API | Advanced lock control | Supports features like tryLock |
| Immutable objects | Avoid shared mutation | Safer design | Often 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.
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.
synchronizedprovides 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 synchronizedand instancesynchronizeddo 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.
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
BankAccountclass with a sharedbalancefield. - Add
depositandwithdrawmethods 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.
Keep learning
Related questions
Add External JAR Files to an IntelliJ IDEA Java Project
Learn how to add external JAR dependencies to an IntelliJ IDEA Java project using module libraries, and when to use Maven or Gradle instead.
Avoiding Java Code in JSP with JSP 2: EL and JSTL Explained
Learn how to avoid Java scriptlets in JSP 2 using Expression Language and JSTL, with examples, best practices, and common mistakes.
Call a Method After a Delay in Android Java
Learn how to run Java code after a delay in Android using Handler.postDelayed, manage the main thread, and cancel callbacks safely.