skip to content

Which individual field and array-element reads and writes does the Java Language Specification guarantee to be atomic, and which ones does it explicitly leave non-atomic?

level: middleimportance: must knowfreq 50%

answer

  1. Refs atomic; all primitives except long/double
  2. long/double may split into two 32-bit halves
  3. volatile long/double always atomic
  4. Atomic ≠ visible, atomic ≠ ordered
  5. One access atomic ≠ x++ atomic

basics

~20 s

Reads and writes are atomic for object references and for every primitive type except long and double. Non-volatile long and double accesses may be split into two 32-bit halves, so a reader can see a mix of two values. Declaring them volatile makes them atomic again.

solid answer

~60 s

The rule lives in the Java Language Specification's section on non-atomic treatment of `double` and `long` (§17.7): - **Atomic:** reads and writes of *references* and of all primitives **except** `long` and `double`. "Atomic" here means indivisible — a read returns some value that some write actually stored, never a blend of two. - **Not guaranteed atomic:** a single non-volatile `long` or `double` read or write, which an implementation may treat as two separate 32-bit actions. A concurrent reader can then observe the high half of one write with the low half of another — a value that was never written by anybody. - **Atomic again if `volatile`:** the specification requires `volatile long` and `volatile double` accesses to be atomic on every implementation. The same applies to array elements: `long[]` and `double[]` elements carry the same caveat. Two boundaries matter. Atomicity is only about one access being indivisible — it says nothing about whether another thread sees it promptly. And it says nothing about compound operations: `x++` is several accesses, each atomic, and still racy.

code

java · 8 lines
java
class Holder {
    Object ref;          // read/write atomic
    int    counter;      // read/write atomic
    double ratio;        // MAY tear: two 32-bit halves allowed
    long   position;     // MAY tear
    volatile long safePos; // atomic by specification, on every platform
    long[] samples;      // each element: same caveat as a long field
}

go deeper

for a junior

Recall the list: references and all primitives except long and double have atomic reads and writes, and volatile fixes those two.

for a middle

State the rule and immediately add the two boundaries — atomicity is not visibility, and one atomic access is not an atomic compound operation.

for a senior

Explain the design rationale (cost on 32-bit platforms, opt-in via volatile), extend it to array elements, and point to AtomicLongArray/VarHandle where element-level atomicity is needed.

for a principal

Frame it as the specification drawing a portability floor rather than describing any one machine, and insist that code be written to the specification rather than to observed 64-bit behaviour.

## What "atomic" means here, precisely In this part of the Java Language Specification, an access being **atomic** means it is *indivisible*: a read observes exactly the bit pattern written by one write, and a write is observed either entirely or not at all. It does not mean "immediately visible", "ordered", or "safe to combine with other operations". Those are separate guarantees with separate rules. ## The guarantee, item by item The specification's rule (JLS §17.7, unchanged in substance for many releases) is: 1. **Reference reads and writes are atomic**, regardless of whether references are 32 or 64 bits wide in a given implementation. A reader sees either the old reference or the new one, never a mixture of their bits. This is essential — a torn reference would be a wild pointer, and the entire memory-safety story of the platform would collapse. 2. **Reads and writes of primitives are atomic, with the explicit exception of `long` and `double`.** So `boolean`, `byte`, `short`, `char`, `int` and `float` accesses are indivisible. 3. **A single non-volatile `long` or `double` read or write may be treated as two separate 32-bit reads or writes.** This is the tearing licence. If one thread writes `0x0000_0000_FFFF_FFFF` over a field previously holding `0x7FFF_FFFF_0000_0000`, a concurrent reader may legally observe `0x7FFF_FFFF_FFFF_FFFF` — a value never written by any thread. 4. **`volatile long` and `volatile double` accesses are always atomic.** Implementations must guarantee this even on platforms whose natural word is 32 bits. Array elements follow the element type: elements of a `long[]` or `double[]` carry the same caveat, elements of an `int[]` do not. ## Why the exception exists at all The specification was written to be implementable on 32-bit machines, where a 64-bit store has no single native instruction guaranteed to be atomic. Rather than force every implementation to pay for a locked or wide instruction on every `long` field access — including in the overwhelmingly common single-threaded case — the language made plain 64-bit accesses cheap and required atomicity only where the programmer asks for it with `volatile`. Making `volatile` the opt-in keeps the cost aligned with the intent. ## What the guarantee does *not* buy you This is where most interview answers go wrong, so be explicit about two boundaries. **Atomicity is not visibility or ordering.** An `int` write is atomic, but another thread may go on reading a stale value indefinitely, and surrounding operations may be observed out of order. Those questions belong to the visibility and ordering parts of the memory model, and are exactly what `volatile`, locks and other synchronisation provide beyond atomicity. A common mistake is to reason "my field is an `int`, so writes are atomic, so no synchronisation is needed" — atomic-but-invisible is a perfectly ordinary outcome of a data race. **Atomicity of each access is not atomicity of a sequence.** `count++` is a read, an add, and a write. Each part is atomic; the combination is not, so two threads can lose an update. Likewise, check-then-act sequences such as "if absent, put" are three or four atomic accesses forming one non-atomic operation. Individual-access atomicity is a *floor*, not a concurrency strategy. ## Practical guidance - If a `long` or `double` field is shared across threads without other synchronisation, mark it `volatile` (or route access through an `AtomicLong` / `VarHandle`). This is correctness by specification, not superstition — do not reason from what your current 64-bit hardware happens to do. - If a field of any type is shared and you need the reader to see updates, you need more than atomicity; you need synchronisation. - If you are combining a read and a write, atomicity of each half is irrelevant. Use a lock, an atomic class, or a `VarHandle` compound operation. - Fields protected by a lock, or confined to one thread, need none of this — atomicity concerns only concurrent unsynchronised access. ## Being precise in the answer A strong answer states the rule as a list — references atomic, primitives atomic except `long`/`double`, `volatile` restores atomicity for those two — and then adds the two disclaimers about visibility and compound actions without being asked. That combination shows you know both what the specification promises and what people wrongly infer from it.

  • A shared `int` field is written by one thread and read by another with no synchronisation. The write is atomic — so is the code correct?
    No. Atomicity only guarantees the reader never sees a half-written value; it says nothing about when, or whether, the reader observes the new value at all. Without synchronisation the reader may keep returning a stale value indefinitely, because nothing establishes the ordering and visibility relationship between the two threads. You need `volatile`, a lock, or another synchronisation mechanism.
  • Does the atomicity rule apply to elements of a `long[]`?
    Yes — an array element behaves like a field of its component type, so `long[]` and `double[]` elements may be treated non-atomically while `int[]` elements are atomic. There is no way to declare an individual array element `volatile`, so for atomic element access you use `AtomicLongArray` or a `VarHandle` produced by `MethodHandles.arrayElementVarHandle`.
  • Why did the language make plain 64-bit accesses non-atomic instead of requiring atomicity everywhere?
    Because on 32-bit platforms a 64-bit store has no cheap guaranteed-atomic instruction, so requiring it would tax every `long` and `double` access — including the vast majority that are never shared — to serve a rare case. Making `volatile` the opt-in puts the cost exactly where the programmer signals that concurrent access matters.

saying these in an interview costs you the question

  • Saying all Java field accesses are atomic without the long/double exception
  • Claiming reference writes can tear on 64-bit platforms
  • Believing atomic writes remove the need for synchronisation (confusing atomicity with visibility)
  • Thinking `volatile` makes `x++` thread-safe
  • Assuming the long/double caveat applies to fields only and not to array elements

context