skip to content

Given AtomicInteger, atomic field updaters, and VarHandle, how do you decide which to use for low-level atomic field access?

level: middleimportance: should knowfreq 28%

answer

  1. AtomicInteger by default — simplest, readable
  2. Field updater = shed per-instance wrapper memory (volatile field, legacy)
  3. VarHandle = new low-level: ordering modes, array CAS, Unsafe replacement
  4. Prefer highest-level tool that works; profile before dropping down
  5. Never sun.misc.Unsafe in new code

basics

~20 s

Default to AtomicInteger/AtomicReference — they are simple and clear. Drop to an atomic field updater only on legacy code or to save memory across huge numbers of instances. For new low-level code that needs custom memory ordering or array-slot CAS, use VarHandle, the modern supported replacement for both updaters and sun.misc.Unsafe.

solid answer

~50 s

Pick the highest-level tool that meets the need. For ordinary thread-safe counters or references, use the Atomic wrapper classes (AtomicInteger, AtomicLong, AtomicReference) — they are the clearest and almost always fast enough. Reach for an atomic field updater only when you must avoid the per-instance wrapper object across an enormous number of instances and you are constrained to a volatile field, or when maintaining pre-Java-9 code that already uses them. For genuinely low-level needs in new code — CAS on a field or array element, weaker memory-ordering modes (acquire/release, opaque), or replacing sun.misc.Unsafe — use VarHandle (Java 9+): it is type-safe, access-controlled via MethodHandles.Lookup, JIT-intrinsified to the same speed as Unsafe, and strictly more capable than the field updaters. In short: AtomicInteger by default, updaters for legacy/memory edge cases, VarHandle for new low-level or ordering-sensitive primitives.

go deeper

for a junior

Should default to AtomicInteger/AtomicReference for thread-safe counters and know they are the simple, safe choice.

for a middle

Can justify AtomicInteger as default and name field updaters (memory/legacy) and VarHandle (modern low-level) as the alternatives with their trade-offs.

for a senior

Articulates the constraints and costs of each and confidently steers new low-level code to VarHandle while explaining when updaters still appear.

for a principal

Sets team guidance: highest-level tool that works, profile before micro-optimizing, ban Unsafe in new code, VarHandle for ordering-sensitive primitives — and weighs the cost of migrating legacy updaters.

## Three tools, one job, different levels All three give lock-free, **compare-and-swap (CAS)**-based atomic access to a value, but at different abstraction levels and ergonomics. CAS = "atomically set the location to a new value only if it currently equals an expected value." Choosing well is mostly about **abstraction level and cost**, not capability. ### 1. Atomic wrapper classes — the default `AtomicInteger`, `AtomicLong`, `AtomicReference` (and array forms `AtomicIntegerArray`, `AtomicReferenceArray`). - **Pros:** simplest, most readable, self-documenting; the field's type makes its atomicity obvious; well-known API (`incrementAndGet`, `compareAndSet`, `updateAndGet`). - **Cons:** each wrapper is a separate heap object (header + the value) referenced by a pointer — extra memory and one more indirection per access. - **Use when:** essentially always, unless a measured constraint pushes you lower. The overhead is irrelevant for a handful of objects. ### 2. Atomic field updaters — the memory edge case `AtomicIntegerFieldUpdater`, `AtomicLongFieldUpdater`, `AtomicReferenceFieldUpdater`. - **What they buy:** atomic ops on a plain **`volatile`** field of your class via a single shared (per-class) updater, so each instance holds only the primitive/volatile field — **no wrapper object per instance**. - **Costs/constraints:** the field must be `volatile`, non-static, and accessible; the updater is built with reflection (fails fast at class init if misused); per-call cost is somewhat higher than a direct Atomic. - **Use when:** you instantiate a class in **very large numbers** and the per-instance wrapper memory matters (lock-free nodes, framework internals), **or** you are maintaining older code that uses them. For new code, VarHandle usually beats them. ### 3. VarHandle — the modern low-level primitive `java.lang.invoke.VarHandle` (Java 9, JEP 193). - **What it buys:** typed, access-checked handles to fields, **array elements**, and buffer locations, with the **full access-mode ladder** (plain/opaque/acquire-release/volatile) plus CAS family — and it is the **supported, safe, equally fast replacement for `sun.misc.Unsafe`** (intrinsified by the JIT). - **Costs:** lower-level and more verbose (static initializer + Lookup); easier to get subtly wrong with weak modes; overkill for ordinary needs. - **Use when:** writing **new** low-level concurrency primitives that need custom memory ordering (e.g. cheap acquire/release publication), per-array-element CAS, or that previously would have used `Unsafe`. ## The decision, distilled | Need | Choose | |---|---| | Ordinary thread-safe counter/reference | **AtomicInteger / AtomicReference** | | Same, but per-instance wrapper memory hurts at huge instance counts (volatile field) | **Atomic field updater** | | Maintaining pre-Java-9 code already using updaters | keep the **updater** | | New low-level primitive: custom ordering modes, array-slot CAS, or replacing Unsafe | **VarHandle** | ## Guiding principles - **Prefer the highest-level tool that works** — readability and maintainability first; drop down only for a measured reason. - **Don't micro-optimize speculatively.** Updaters and VarHandle are sharp tools; profile before reaching for them. - **For new low-level code, prefer VarHandle over updaters** — it is faster, more general, type-safe, and the platform's sanctioned path; updaters live on mainly for legacy. - **Never use `sun.misc.Unsafe` in new code** — VarHandle exists precisely so you don't have to. ## Summary Use `AtomicInteger`/`AtomicReference` by default; use an atomic **field updater** only to shed per-instance wrapper memory across huge instance counts (or in legacy code), accepting the volatile-field/reflection constraints; and use **`VarHandle`** for new low-level work needing custom memory ordering, array-element CAS, or as the safe replacement for `sun.misc.Unsafe`. The rule is to pick the highest-level option that satisfies a real, measured requirement.

  • Why might you still see atomic field updaters in modern codebases?
    Legacy: they predate VarHandle (pre-Java-9) and remain in older libraries and JDK internals; rewriting working, tested lock-free code carries risk, so they persist even though new code would use VarHandle.
  • What capability does VarHandle have that AtomicInteger and the field updaters do not?
    Explicit control over memory-ordering strength via access modes (plain/opaque/acquire-release/volatile) and uniform support for fields, array elements, and buffer locations — the Atomic classes and updaters effectively only expose volatile-strength operations.

saying these in an interview costs you the question

  • Reaching for VarHandle/updaters for an ordinary counter where AtomicInteger is fine
  • Recommending atomic field updaters over VarHandle for new code
  • Suggesting sun.misc.Unsafe at all in new code
  • Claiming field updaters are faster per-call than AtomicInteger (they save memory, not time)
  • Assuming VarHandle is slower than the alternatives because it looks low-level

context