Fundamentals 7 min read

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.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Is i++ Thread‑Safe? Understanding Volatile, Visibility, and Atomic Operations in Java

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.

JavaConcurrencyThread SafetyvolatileAtomicInteger
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.