Thread synchronization in Java is a mechanism that ensures two or more threads do not simultaneously access a shared resource, which could lead to inconsistent data or race conditions. Java provides several ways to handle thread synchronization.
Here’s a detailed explanation:
1. Why Synchronization is Needed
Consider two threads trying to update the same bank account balance:
class Account {
int balance = 1000;
void deposit(int amount) {
balance += amount;
}
}
If two threads call deposit()
at the same time, the final balance may be incorrect due to race conditions.
2. Using synchronized
Keyword
a) Synchronized Method
Marking a method as synchronized
ensures that only one thread at a time can execute that method on the same object.
class Account {
int balance = 1000;
synchronized void deposit(int amount) {
balance += amount;
}
synchronized void withdraw(int amount) {
balance -= amount;
}
}
- Instance method: locks on the current object (
this
). - Static method: locks on the
Class
object.
b) Synchronized Block
You can synchronize only a part of the code to reduce overhead.
class Account {
int balance = 1000;
void deposit(int amount) {
synchronized(this) { // lock on the current object
balance += amount;
}
}
}
- Locks can also be on other objects for finer control:
synchronized(lockObject) { // critical section }
3. Lock Objects (java.util.concurrent.locks
)
Java provides more advanced locking mechanisms via Lock interface.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Account {
int balance = 1000;
Lock lock = new ReentrantLock();
void deposit(int amount) {
lock.lock(); // acquire lock
try {
balance += amount;
} finally {
lock.unlock(); // release lock
}
}
}
Advantages of Lock
over synchronized
:
- Can try to acquire lock without blocking (
tryLock()
). - Can interrupt a thread waiting for a lock.
- More flexible and scalable.
4. Volatile Keyword
volatile
ensures visibility of changes across threads, but it does not provide atomicity.
class SharedResource {
volatile int count = 0;
void increment() {
count++; // Not atomic, still need synchronization for correctness
}
}
Use volatile
when you only need to read/write a single variable and not perform compound operations.
5. Other Concurrency Utilities
Java’s java.util.concurrent
package provides ready-made thread-safe structures:
AtomicInteger
,AtomicLong
– for atomic operations on integers.ConcurrentHashMap
– thread-safe map.CopyOnWriteArrayList
– thread-safe list.
Example:
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
AtomicInteger count = new AtomicInteger(0);
void increment() {
count.incrementAndGet();
}
}
✅ Key Points
- Synchronization prevents race conditions.
synchronized
ensures mutual exclusion.- Synchronized methods lock on the object (or class if static).
- Synchronized blocks can lock on any object.
Lock
interface offers more flexibility.volatile
guarantees visibility but not atomicity.- Use
java.util.concurrent
utilities for high-performance concurrency.