Last Updated : 23 Jul, 2025
In Java, multithreading can lead to challenges related to thread safety and data consistency. Java provides concurrency mechanisms like Atomic, Volatile, and Synchronized to address these issues and ensure thread safety. These mechanisms offer unique advantages and limitations.
Atomic vs Volatile vs SynchronizedFeatures
Synchronized Volatile AtomicApplies to
It applies to only blocks or methods. It applies to variables only. It is also applicable to variables only.Purpose
It ensures mutual exclusion and data consistency by acquiring locks. It ensures the visibility of variables across threads but does not guarantee atomicity It provides atomic operations on variables without needing locksPerformance
Performance is relatively low compared to volatile and atomic keywords because of the acquisition and release of the lock. Performance is relatively high compared to synchronized Keyword. Performance is relatively high compared to both volatile and synchronized keyword.Concurrency
Because of its locking nature, it is not immune to concurrency hazards such as deadlock and livelock. Because of its non-locking nature, it is immune to concurrency hazards such as deadlock and livelock. Because of its non-locking nature, it is immune to concurrency hazards such as deadlock and livelock. Synchronized ModifierThe synchronized
keyword in Java is used to control access to shared resources among multiple threads. It can be applied to methods or blocks of code. When a method or block is declared as synchronized
, only one thread can execute it on a given object at a time. This ensures data consistency and prevents race conditions.
Example:
Java
// Java program to demonstrate
// the synchronized modifier
import java.util.concurrent.TimeUnit;
class CounterThread extends Thread {
private int count;
// Synchronized method to prevent race conditions
@Override
public synchronized void run() {
try {
TimeUnit.MILLISECONDS.sleep(100); // Simulate work with sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
count++; // Increment the count
}
public int getCount() {
return count;
}
}
public class Geeks {
public static void main(String[] args) throws InterruptedException {
CounterThread t1 = new CounterThread();
CounterThread t2 = new CounterThread();
t1.start();
t2.start();
t1.join();
t2.join();
// Expected output is 1 because only
// one thread can increment at a time
System.out.println("Count: " + t1.getCount());
}
}
Explanation: In the above example, the synchronized method ensures that only one thread can execute the run() method at a time, preventing race conditions. Though two threads attempting to increment the count concurrently, the synchronization ensures they are executed sequentially.
Volatile ModifierThe 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.
Working of Volatile Modifier:Example:
Java
// Java program to demonstrate the
// volatile modifier with non-atomic increment
class Counter {
private volatile int count; // Volatile variable
public void increment() {
count++; // This operation is not atomic
}
public int getCount() {
return count;
}
}
public class Geeks {
public static void main(String[] args) {
Counter c = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
c.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
c.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + c.getCount());
}
}
Explanation: In the above example, the volatile variable is count. While volatile ensures visibility of changes across threads and the increament operation is non-atomic, race condition can still occur. The expected result may be less than or equal to 2000.
Atomic ModifierAtomic classes, such as AtomicInteger, are part of the java.util.concurrent.atomic package. These classes provide thread-safe operations on variables without the need for synchronization. They use low-level atomic operations like compare-and-swap (CAS) to ensure thread safety.
Working of Atomic Modifier:Example:
Java
import java.util.concurrent.atomic.AtomicInteger;
class CounterThread extends Thread {
private AtomicInteger count = new AtomicInteger(); // Atomic variable
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(i * 50); // Simulate work with sleep
count.incrementAndGet(); // Atomic increment operation
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getCount() {
return count.get(); // Get current value of count
}
}
public class Geeks {
public static void main(String[] args)
throws InterruptedException {
CounterThread t1 = new CounterThread();
CounterThread t2 = new CounterThread();
t1.start();
t2.start();
t1.join();
t2.join();
// Expected output is 10 because
// each thread increments 5 times.
System.out.println("Count: " + (t1.getCount() + t2.getCount()));
}
}
Explanation: In the above example, the AtomicInteger class ensures that the incrementAndGet() operation is atomic across multiple threads. Each thread increments the count 5 times, so the final output will always be 10 (5 increments per thread).
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4