Do you understand this snippet?

do {
    oldseed = seed.get();
    newseed = (oldseed * ...) & mask;
} while (!seed.compareAndSet(oldseed, newseed));   // ← CAS

Level 0: First understand why a “variable” is dangerous in multithreading

Suppose there’s a variable seed = 100, and two threads want to change it at the same time:

Thread A: reads seed=100 → computes new value 200 → writes back seed=200
Thread B: reads seed=100 → computes new value 300 → writes back seed=300

Here’s the problem — both read 100, each computes on its own, and whoever writes later wins. A’s result is overwritten by B, A’s work is wasted, and the program has no idea anything went wrong.

This is the classic thread-safety problem.


Level 1: There are two roads to a solution

Road one: pessimistic lock (synchronized)
  → "I'll lock the door first, nobody else comes in"
  → safe but slow (others have to queue)

Road two: optimistic lock (CAS)
  → "The door isn't locked, but before I write I'll verify; if someone changed it, I retry"
  → fast but may require retries

This snippet takes road two.


Level 2: What is CAS?

CAS = Compare And Set, with three parameters:

CAS(memory address, the old value I expect, the new value I want to write)

What it does, in plain language:

“I remember this value is 42. If it’s still 42 now, change it to 77 for me. If it’s no longer 42, someone touched it, so do nothing and tell me it failed.”

The key point: this whole “compare + write” is done in a single CPU instruction and cannot be interrupted. That’s the fundamental reason it can guarantee thread safety.


Level 3: Dissecting the code line by line

do {
    oldseed = seed.get();                        // ① take a snapshot
    newseed = (oldseed * ...) & mask;            // ② compute the new value
} while (!seed.compareAndSet(oldseed, newseed)); // ③ CAS attempt to write

seed.get()

seed is an AtomicLong, and its get() simply reads the current value. Storing the current value into oldseed is like taking a photo — “this is the value I saw.”

② Compute the new value

Compute based on the old value. Here it’s the pseudo-random formula (linear congruential); the details don’t matter, the key is: the new value depends on the old value.

compareAndSet(oldseed, newseed)

This is the CAS operation:

  • Compare: is seed’s current value still oldseed?
  • Yes → change seed to newseed, return true
  • No → do nothing, return false

while(!...) spin

CAS returns true → !true = false → break out of the loop, done. CAS returns false → !false = true → continue the loop, start over from ①.


Level 4: Walking through it with a real-life scenario

flowchart TD
    A["Start"] --> B["① Read the current value
    oldseed = seed.get()
    e.g. reads 100"]
    B --> C["② Compute the new value
    newseed = f(100) = 777"]
    C --> D{"③ CAS verify
    is seed still 100 now?"}
    D -->|"Yes → nobody touched it
    write 777, return true"| E["Success! Break the loop"]
    D -->|"No → someone changed it
    return false"| F["Wasted computation, start over"]
    F --> B

Level 5: What exactly happens when two threads compete?

Timeline:

Time 1: seed = 100

Time 2: Thread A reads oldseed=100
Time 3: Thread B reads oldseed=100

Time 4: Thread A computes newseed=777
Time 5: Thread B computes newseed=888

Time 6: Thread A executes CAS(100, 777)
        → is seed still 100? Yes! → change to 777 → success ✅

Time 7: Thread B executes CAS(100, 888)
        → is seed still 100? No, it's already 777
        → failure ❌

Time 8: Thread B goes another round
        → reads oldseed=777
        → computes newseed=999
        → CAS(777, 999) → success ✅

Note: no data was overwritten, no result was lost. This is CAS’s safety guarantee.


Level 6: Why not just use a lock?

                  Locking (synchronized)      CAS spin
─────────────────────────────────────────────────────
When someone holds it   thread suspended (sleeps)   thread spins (retries repeatedly)
Recovery cost           waking the thread ≈ a few µs  no cost, just the next loop
Low contention          the lock overhead is wasted   extremely fast, near-zero overhead
High contention         queue and wait, stable        spinning wastes CPU
Suited for              long-running operations       extremely fast operations (nanosecond-level)

The operation in this snippet is one multiplication and one bit operation — done in nanoseconds — so CAS is far faster than locking.


Level 7: What does this code actually do?

This code comes from Java’s java.util.Random or ThreadLocalRandom; it generates pseudo-random numbers.

seed is the random number’s “seed,” and every call to nextInt() has to:

  1. Get the current seed
  2. Compute the next seed from it (the linear congruential formula)
  3. Update the seed using CAS
  4. Compute a random number from the new seed and return it to you

CAS is used because multiple threads may be calling nextInt() at the same time, and each thread must get a different seed; otherwise duplicate “random” numbers would be generated.