Is i++ Thread‑Safe? Understanding Volatile, Visibility, and Atomic Operations in Java
This article explains why the i++ operation is not thread‑safe in Java, examines how the volatile keyword affects variable visibility, demonstrates the problem with concrete code examples, and shows how to solve it using synchronized blocks or the java.util.concurrent.atomic classes.
Many Java interviewees are asked whether the i++ operation is thread‑safe. The answer is no, because i++ is not an atomic operation; it consists of a read, an increment, and a write, which can interleave between threads.
The volatile keyword guarantees that writes to a variable are immediately flushed to main memory and reads always fetch the latest value, solving the visibility problem but not atomicity. Each thread has its own local memory, and the timing of synchronization to main memory is nondeterministic.
Example without volatile shows two threads reading the same initial value of i , both incrementing it, and writing back 1 , resulting in a final value of 1 after two increments.
When volatile is added, the same interleaving can still occur; the variable becomes visible across threads but the increment operation remains non‑atomic, so thread‑safety is not achieved.
Bytecode for a simple i++ method demonstrates the multiple instructions involved ( aload_0 , getfield , iconst_1 , iadd , putfield ), confirming that the operation is not a single atomic step.
To correctly handle concurrent increments, Java provides the java.util.concurrent.atomic package. Using AtomicInteger (or synchronized blocks) ensures atomic updates at the cost of additional overhead.
public class SafeTest {
private static AtomicInteger count = new AtomicInteger(0);
private static final int times = Integer.MAX_VALUE;
public static void main(String[] args) throws Exception {
Thread decThread = new DecThread();
decThread.start();
for (int i = 0; i < times; i++) {
count.incrementAndGet();
}
while (decThread.isAlive());
System.out.println("Result: " + count);
}
private static class DecThread extends Thread {
@Override
public void run() {
for (int i = 0; i < times; i++) {
count.decrementAndGet();
}
}
}
}Running the above code with two threads (one incrementing, one decrementing) yields a final result of 0 , demonstrating correct synchronization.
Conclusion: volatile solves visibility but not atomicity; to make i++ thread‑safe you must use synchronized or atomic classes such as AtomicInteger , both of which introduce performance overhead.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.