Why Incrementing a Counter Isn’t Thread‑Safe in Java: A Deep Dive into Atomicity
This article explains Java’s atomicity and thread‑safety concepts, demonstrates how a simple volatile counter increment isn’t atomic, and shows a multithreaded test that produces inconsistent results, illustrating why proper synchronization is required for safe concurrent access.
Concepts
Atomicity : an operation (or group of operations) must either complete entirely without interruption or not at all.
Thread safety : when multiple threads access a class, a locking mechanism protects shared data so that only one thread can modify it at a time, preventing inconsistent or polluted data.
Thread‑unsafe : no protection is provided; concurrent modifications can produce dirty or incorrect data.
When a class can be called from multiple threads without external synchronization, it must be thread‑safe. To achieve this, each thread‑safe method must be atomic, meaning other threads can only observe the state before or after the method execution, never an intermediate state.
Code Example
public class TR extends FanLibrary {
private volatile int i = 0;
public void ss() {
sleep(100); // added to increase the chance of race condition
i++;
}
@Before
public void be() {
output("before");
}
@Test
public void sdfa() throws InterruptedException {
Thread first = new Thread(() -> {
ss();
});
Thread second = new Thread(() -> {
ss();
});
first.start();
second.start();
first.join();
second.join();
output(i);
}
@After
public void ds() {
output("after");
}
}The test creates two threads that both invoke ss(). The method sleeps for 100 ms to make the race condition more likely, then increments the volatile field i.
Console Output
INFO-> before
INFO-> 1
INFO-> afterThe output shows that the final value of i is 1, even though the increment operation was executed twice. The statement i++ expands to i = i + 1, which consists of a read, an addition, and a write. Because these steps are not atomic, both threads can read the initial value 0, compute 1, and then each write 1 back, leaving the counter at 1 instead of the expected 2.
This demonstrates a classic race condition: when two threads simultaneously obtain the same initial value, they both perform the non‑atomic update, resulting in lost updates. Proper synchronization (e.g., using AtomicInteger or synchronized blocks) is required to make the increment operation truly atomic and thread‑safe.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
