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 wrongSynchronization 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
| Style | Meaning | Best when |
|---|---|---|
| synchronized method | Locks the whole method | Method is short and all of it needs safety |
| synchronized block | Locks only a small part | You 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 countsFor maps and lists shared by many threads, prefer:
ConcurrentHashMapinstead of synchronizing a normalHashMapCopyOnWriteArrayListwhen 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.
