skip to content

How do you use ThreadLocalRandom correctly, and what problem does it solve compared to a shared Random?

level: middleimportance: should knowfreq 45%

answer

  1. current() at point of use, use immediately
  2. never store/share the instance across threads
  3. fixes CAS contention + cache-line bouncing
  4. setSeed throws -> not reproducible
  5. bounded overloads: nextInt(origin, bound)

basics

~20 s

Call ThreadLocalRandom.current() each time you need it and use the result right away; never save it and pass it to other threads. It avoids the slowdown you get when many threads share one Random object.

solid answer

~40 s

A single Random shared by many threads becomes a bottleneck: every call updates the same internal state via a compare-and-set retry loop, so threads contend and throughput drops under load. ThreadLocalRandom gives each thread its own generator. You don't construct it — you call the static factory ThreadLocalRandom.current(), which returns the calling thread's instance, and you use it immediately, e.g. ThreadLocalRandom.current().nextInt(0, 100). The two key rules: never share the returned instance across threads (that reintroduces contention and correctness issues), and don't rely on seeding it for reproducibility — setSeed throws UnsupportedOperationException. It also offers handy bounded overloads like nextInt(origin, bound) and nextDouble(origin, bound). It's a fast statistical PRNG, so it's not for security.

go deeper

for a junior

Knows ThreadLocalRandom is a 'faster Random for threads' and uses current().

for a middle

Uses current() correctly, knows not to share the instance, and can state it solves contention vs a shared Random.

for a senior

Explains the CAS/cache-line contention mechanism, the no-reproducible-seed constraint, and chooses it appropriately vs Random/SecureRandom.

for a principal

Sets concurrency conventions (jitter/backoff sampling), reasons about scalability under core count, and the Java 17 RandomGenerator unification.

### The problem: contention on a shared Random `java.util.Random` is thread-safe: its 48-bit state is updated with an atomic **compare-and-set (CAS)** loop. CAS means 'read the current value, compute the next, and only write it back if no one else changed it in the meantime; otherwise retry'. With one thread that's fine. With many threads all calling the *same* `Random`, they constantly invalidate each other's CAS attempts and retry, and they all touch the **same cache line**, causing cache-coherency traffic between CPU cores. The result: as you add threads, random-number throughput stalls instead of scaling. ### The fix: a generator per thread `ThreadLocalRandom` (Java 7+) eliminates sharing. Conceptually each thread carries its **own** PRNG state, so there is no shared memory to contend on and no locking. That's why it scales with cores. ### How to use it correctly ```java int dice = ThreadLocalRandom.current().nextInt(1, 7); // 1..6 double u = ThreadLocalRandom.current().nextDouble(); // [0.0, 1.0) ``` Rules that follow from its design: - **Always obtain it via `ThreadLocalRandom.current()`** at the point of use. The returned object is *the current thread's* instance. - **Never store the instance in a field or pass it to another thread.** If thread B uses thread A's instance, you lose the per-thread isolation — you get contention back *and* potential incorrectness. (This is the single most common mistake.) - **It isn't reproducible:** `setSeed(...)` throws `UnsupportedOperationException`. If you need a fixed seed for tests, use `Random` instead. - **Bounded helpers:** `nextInt(origin, bound)`, `nextLong(origin, bound)`, `nextDouble(origin, bound)` give half-open ranges `[origin, bound)` without manual modulo bias handling. - **Not for security:** it's a fast statistical PRNG, not a CSPRNG. Use `SecureRandom` for tokens/keys. ### Where it fits Use `ThreadLocalRandom` in any concurrent workload that needs random numbers — parallel simulations, load generators, randomized backoff/jitter in retry logic, sampling in concurrent data structures. For single-threaded code, plain `Random` is fine; the contention problem doesn't exist there. ### Modern note Since Java 17, all these generators implement the `RandomGenerator` interface; `ThreadLocalRandom.current()` still returns the per-thread generator, and the usage rules above are unchanged.

  • Why is sharing the instance returned by current() across threads wrong?
    It defeats the per-thread design — you reintroduce shared-state contention and risk incorrect/biased results. Each thread must call current() to get its own generator.
  • What does nextInt(10, 20) return?
    A uniformly random int in the half-open range [10, 20) — i.e. 10 through 19 inclusive.

saying these in an interview costs you the question

  • Caching ThreadLocalRandom.current() in a field/singleton and reusing it from many threads
  • Trying to seed it for deterministic tests
  • Using it for security tokens
  • Thinking a shared Random is fine at high concurrency because it's 'thread-safe'

context