When working with multithreading in Java, ensuring thread safety becomes crucial. Java provides different mechanisms, such as synchronized, volatile, and atomic variables, to handle shared data across threads. While all three help in managing concurrent access, they differ significantly in what they provide:
- synchronized ensures mutual exclusion and visibility.
- volatile ensures visibility only (not synchronization or mutual exclusion).
- atomic variables provide lock-free, thread-safe operations for specific actions.
Synchronized
Synchronized keyword ensures that only one thread at a time can execute a particular method or block of code on a given object. It provides:
- Mutual exclusion: one thread at a time executes the synchronized code.
- Visibility: changes made by one thread become visible to others after the lock is released.
public class GFG{
private int count = 0;
public synchronized void increment(){
// atomic due to synchronization
count++;
}
public int getCount() { return count; }
public static void main(String[] args)
throws InterruptedException
{
GFG demo = new GFG();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++)
demo.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++)
demo.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count (synchronized): " + demo.getCount());
}
}
Output
Final count (synchronized): 2000
Explanation: The synchronized keyword ensures only one thread executes increment() at a time, guaranteeing atomicity and visibility.
Volatile
The volatile keyword in Java ensures that all threads have a consistent view of a variable's value. It prevents caching of the variable's value by threads, ensuring that updates to the variable are immediately visible to other threads.
public class VolatileExample {
// ensures visibility, not atomicity
private volatile int count = 0;
public void increment(){
// not atomic (read + modify + write)
count++;
}
public static void main(String[] args)
throws InterruptedException
{
VolatileExample demo = new VolatileExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++)
demo.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++)
demo.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count (volatile): " + demo.count);
}
}
Output
Final count (volatile): 2000
Explanation: Even though count is volatile, the operation count++ is not atomic — it consists of three steps: read, increment, and write. Thus, multiple threads can interleave these steps, leading to incorrect results.
Atomic keyword
Atomic Variables provide lock-free, thread-safe operations on single variables. They ensure atomicity and visibility using low-level Compare-And-Swap (CAS) operations without using synchronization.
import java.util.concurrent.atomic.AtomicInteger;
public class GFG{
private AtomicInteger count = new AtomicInteger(0);
public void increment()
{
count.incrementAndGet(); // atomic, lock-free
// increment
}
public int getCount() { return count.get(); }
public static void main(String[] args)
throws InterruptedException
{
GFG demo = new GFG();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++)
demo.increment();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++)
demo.increment();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count (AtomicInteger): " + demo.getCount());
}
}
Output
Final count (AtomicInteger): 2000
Explanation: AtomicInteger ensures atomic updates through CAS operations. It achieves thread safety without locks, making it more efficient than synchronized for simple variable updates.
Synchronized vs Volatile vs Atomic
| Feature | Synchronized | Volatile | Atomic |
|---|---|---|---|
| Applies to | Methods/blocks | Variables | Variables |
| Purpose | Ensures mutual exclusion and consistency (via locks) | Ensures visibility (no atomicity) | Provides atomic operations (no locks) |
| Performance | Lower (due to locking) | Higher than synchronized | Higher than both synchronized and volatile |
| Concurrency | Prone to deadlocks/livelocks | Immune (no locks) | Immune (no locks) |