Back to Advanced: Disruptor, Actor, False Sharing
Q3Question 3 of 5

What is busy-waiting?

Key Concepts
  • Busy-waiting (spinning) is checking a condition in a tight loop instead of blocking and yielding the CPU.
  • A spin lock uses CAS in a loop to acquire a flag, avoiding the cost of suspending and resuming.
  • Thread.onSpinWait() (Java 9) hints the CPU to optimize the spin loop (x86 PAUSE instruction).
  • Spinning helps when the expected wait is shorter than a context switch and a spare core is available.
  • Spinning harms when the wait is long, cores are oversubscribed, or the holder shares the same core.
  • Bounded/adaptive spinning spins a limited number of times then falls back to parking.
  • The JVM's own locks already blend short spinning with blocking, so raw spin locks are rarely needed.

Definition

Spinning trades CPU cycles to avoid a context switch, so it helps only for very short waits on an available core; use Thread.onSpinWait to hint the CPU, and block instead when the wait is long or cores are scarce.
  • Busy-waiting (spinning) – repeatedly checking a condition in a tight loop instead of blocking.
  • Spin lock – mutual exclusion via CAS in a loop, avoiding thread suspension.
  • Thread.onSpinWait() – Java 9 hint to the CPU (x86 PAUSE instruction) that reduces power and improves sibling hyperthread performance.
  • Helps when – wait is sub-microsecond, spare core is available, contention is low.
  • Harms when – wait is long, cores are oversubscribed, or spinner shares a core with the holder.
  • Bounded spinning – spin a limited number of times, then fall back to parking.

A spinning teller stands at the vault door jiggling the handle every microsecond instead of going on break. If the door opens in a blink, this is faster than being paged back. If the door is locked for ten minutes, the teller wastes energy and blocks the corridor. onSpinWait is tapping gently instead of yanking hard.

Understanding the Concepts

What is busy-waiting or spinning?

Busy-waiting means a thread sits in a tight loop checking a condition instead of yielding the CPU and sleeping. The CPU core running the spinner does no useful work during this time, but the advantage is zero wake-up latency when the condition becomes true.

What is a spin lock?

A spin lock is a mutual-exclusion mechanism built from CAS in a loop. The thread spins until the CAS succeeds, meaning it acquired ownership.

JAVA
package com.bank.lock;

import java.util.concurrent.atomic.AtomicBoolean;

public class SimpleSpinLock {

    private final AtomicBoolean locked = new AtomicBoolean(false);

    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            Thread.onSpinWait();  // hint: this is a spin-wait
        }
    }

    public void unlock() {
        locked.set(false);
    }
}

What does Thread.onSpinWait() do?

It is purely a performance hint introduced in Java 9. On x86 it maps to the PAUSE instruction, which reduces power consumption, avoids a memory-order violation penalty when the spinning variable changes, and improves sibling hyperthread throughput by freeing execution resources.

When does spinning help?

Three conditions must all hold for spinning to pay off. First, the expected wait is sub-microsecond, shorter than a context switch. Second, a spare core is available so the spinner does not compete with the lock holder. Third, contention is low so only one or two threads spin at a time.

When does spinning harm?

When the wait is long, the spinner burns a whole core doing nothing productive. When cores are oversubscribed, the spinner can prevent the lock holder from running, extending the critical section. On a laptop with 4 cores and 8 threads processing settlements, two spinners can halve throughput.

What is bounded or adaptive spinning?

Bounded spinning spins a limited number of iterations and then falls back to parking, combining the low-latency benefit of spinning with the CPU efficiency of blocking.

JAVA
package com.bank.lock;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

public class BoundedSpinLock {

    private final AtomicBoolean locked = new AtomicBoolean(false);
    private static final int MAX_SPINS = 100;

    public void lock() {
        int spins = 0;
        while (!locked.compareAndSet(false, true)) {
            if (++spins < MAX_SPINS) {
                Thread.onSpinWait();                    // bounded spin phase
            } else {
                LockSupport.parkNanos(1_000);           // fall back to parking
                spins = 0;
            }
        }
    }

    public void unlock() {
        locked.set(false);
    }
}

How do JVM locks already handle this?

The JVM's intrinsic locks (synchronized) and java.util.concurrent locks blend short spinning with blocking automatically. The HotSpot JVM performs adaptive spinning: it tracks whether spinning recently succeeded for a given lock and adjusts the spin count. In application code you almost never need a raw spin lock.

How does spinning apply to a banking trade sequencer?

At National Bank of India, the trade sequencer assigns monotonically increasing sequence numbers. The critical section is a single AtomicLong.incrementAndGet() held for under 100 nanoseconds. A spin lock is justified here because the hold time is far shorter than a context switch, and the sequencer runs on a dedicated core.

JAVA
package com.bank.trading;

import java.util.concurrent.atomic.AtomicLong;

public class TradeSequencer {

    private final AtomicLong sequence = new AtomicLong(0);

    public long nextSequence() {
        return sequence.incrementAndGet();
        // CAS internally spins; hold time is nanoseconds
    }
}

What is the difference between Thread.onSpinWait() and Thread.yield()?

| Aspect | Thread.onSpinWait() | Thread.yield() |
|--------|--------------------|-----------------|
| Introduced | Java 9 | Java 1.0 |
| CPU effect | PAUSE instruction, saves power | Scheduler hint, may be ignored |
| Thread state | Stays RUNNABLE | Stays RUNNABLE |
| Use case | Spin-wait loops | Discouraged in production |
| Hyperthread benefit | Yes, frees execution resources | No |

What are common mistakes?

Using an unbounded spin loop without onSpinWait wastes power and hurts hyperthreaded cores. Another mistake is spinning on a condition that depends on IO completion, where the wait could be milliseconds or more.

JAVA
// WRONG: spinning on IO completion
while (!fileWriteComplete) {
    Thread.onSpinWait(); // could spin for milliseconds, wasting a core
}

// CORRECT: park and be notified
synchronized (lock) {
    while (!fileWriteComplete) {
        lock.wait();
    }
}

What are common interview follow-up questions?

  • How does the Disruptor use spinning for low-latency messaging?
  • When is spinning appropriate for a trading engine?
  • How does adaptive spinning in HotSpot decide when to stop spinning?

Visual Overview

DIAGRAM
  Spin Lock Timeline:

  Thread A (holder):  ──lock──work──unlock──
  Thread B (spinner): ──CAS fail──CAS fail──CAS fail──CAS succeed──work──
                           │         │         │         │
                        onSpinWait onSpinWait onSpinWait (acquired)

  Bounded Spinning:
  ┌──────────────────────────────────────┐
  │ Spin N times with onSpinWait         │
  │    │                                  │
  │    ▼ still not acquired?              │
  │ Fall back to LockSupport.parkNanos() │
  │    │                                  │
  │    ▼ wake up, try again               │
  └──────────────────────────────────────┘

  Decision:
  Wait < context switch + spare core → spin
  Wait > context switch or no spare core → block

Key Takeaway

Spinning checks a condition in a tight loop instead of blocking. A spin lock uses CAS in a loop. Thread.onSpinWait hints the CPU to conserve power during the spin.

Spinning helps when the wait is sub-microsecond and a spare core is available. It harms when the wait is long or cores are oversubscribed. The pragmatic middle ground is bounded spinning: spin a limited number of times then park.

At National Bank of India the one place I used an explicit spin lock was the trade sequencer, where the critical section was a single increment held for under a microsecond with spare cores available.

Remember one sentence: spinning trades CPU cycles to dodge a context switch, so it only pays off for provably tiny waits on an available core; always add Thread.onSpinWait and block when the wait could be long.

---

Share
Use ← → arrow keys to navigate