Fundamentals 9 min read

Understanding Ordering Issues and Volatile in Java Concurrency

This article explains why intuition fails in multithreaded Java programs, demonstrates ordering problems with simple thread examples, shows how instruction reordering and JIT optimizations can produce unexpected results, and presents the volatile keyword and jcstress testing as reliable solutions to ensure correct visibility and ordering.

High Availability Architecture
High Availability Architecture
High Availability Architecture
Understanding Ordering Issues and Volatile in Java Concurrency

Concurrent programming is notoriously difficult because developers must reason about both the optimal concurrency model (e.g., single‑threaded Redis, multi‑process Nginx, or Netty’s Reactor) and low‑level memory‑visibility issues such as atomicity, visibility, and ordering, which often invalidate intuition.

The article starts with a simple two‑thread example where thread T1 sets data = 666 and then isReady = true , while thread T2 spins on while (!isReady) {} before reading data . Theoretical reasoning suggests T2 should always see data = 666 and compute r = 888 , but real executions can produce r = 222 due to instruction reordering.

Running the program repeatedly on a standard JVM shows only the expected result, yet hidden reordering can still occur because the compiler and CPU are free to reorder writes for performance. The article illustrates this with a diagram where the write to isReady may be observed before the write to data , breaking the assumed order.

To expose the problem, the author uses the OpenJDK jcstress framework. A test class annotated with @JCStressTest defines two actors: one writes data = 666 then isReady = true , the other either checks isReady with an if or a while loop and records the result. The outcomes 888 (expected) and 0 (acceptable) appear, while occasional 222 reveals the reordering bug.

The article explains why the while (!isReady) {} loop can become an infinite spin: the JIT may cache the value of isReady after the first read, never re‑loading it from memory, so the loop never observes the write.

The fix is to declare isReady as volatile . The volatile keyword prevents both compiler and CPU from reordering the write to data after the write to isReady , and it forces each read to fetch the latest value from main memory, eliminating visibility problems.

Finally, the article summarizes that writing correct concurrent Java code requires understanding these ordering pitfalls, using tools like jcstress for systematic testing, and applying volatile (or higher‑level constructs) to enforce the intended execution order.

JavaConcurrencyvolatileMemory Modelinstruction reorderingjcstress
High Availability Architecture
Written by

High Availability Architecture

Official account for High Availability Architecture.

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.