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:
- Get the current seed
- Compute the next seed from it (the linear congruential formula)
- Update the seed using CAS
- 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.