Why ThreadLocalRandom Beats Random in Java: Deep Dive into Unsafe and Memory

This article explores the performance drawbacks of java.util.Random in high‑concurrency scenarios, explains how ThreadLocalRandom leverages Unsafe for per‑thread seeds, examines native getLong/putLong operations, and discusses memory layout nuances such as compressed oops and potential pitfalls.

Programmer DD
Programmer DD
Programmer DD
Why ThreadLocalRandom Beats Random in Java: Deep Dive into Unsafe and Memory

Preface

While writing business code I needed to generate random numbers, so I first considered the JDK Random class. Seeking maximum performance, I turned to ThreadLocalRandom and, while inspecting its implementation, also examined parts of Unsafe, learning many details and solving numerous doubts, which I now summarize.

Random Performance Issues

In typical usage a single Random instance is stored as a field or static variable to avoid repeated construction. This works when thread contention is low, but in a high‑concurrency web service sharing one Random can cause threads to block because Random updates its seed via CAS, leading to repeated CAS failures under contention.

ThreadLocalRandom

The JDK developers addressed this by adding ThreadLocalRandom in the java.util.concurrent package. Although its name suggests a ThreadLocal implementation, the source contains no ThreadLocal fields; instead it relies heavily on Unsafe operations.

Core code snippet:

<code>UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA);</code>

Translated to more familiar Java:

Thread t = Thread.currentThread();
long r = UNSAFE.getLong(t, SEED) + GAMMA;
UNSAFE.putLong(t, SEED, r);

The pattern resembles a Map get/set where the current thread object serves as the key and the seed as the value.

Unsafe

Functionality

The relevant native methods are:

public native long getLong(Object obj, long offset);
public native void putLong(Object obj, long offset, long value);
putLong

writes a 64‑bit value at the given memory offset of an object, while getLong reads a 64‑bit value from that offset.

Safety Concerns

Because Unsafe can manipulate memory without safety checks, misuse can cause a fatal JVM error. For example:

Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Test test = new Test();
test.ttt = "12345";
unsafe.putLong(test, 12L, 2333L);
System.out.println(test.value);

Running this code triggers a fatal error: the JVM aborts because the memory region originally holding a String is overwritten with a long, corrupting the object header.

ThreadLocalRandom Implementation

ThreadLocalRandom

stores a per‑thread seed in the private field threadLocalRandomSeed of the Thread class. The offset of this field is obtained at class‑loading time via:

SEED = UNSAFE.objectFieldOffset(Thread.class.getDeclaredField("threadLocalRandomSeed"));

Since the object layout is fixed after class loading, using Unsafe.objectFieldOffset to locate the seed field and then reading/writing it with getLong / putLong is safe and avoids the overhead of accessor methods.

Open Questions

Why Use Unsafe Instead of Get/Set?

Adding public getter/setter methods to Thread would break encapsulation because ThreadLocalRandom resides in a different package. Using Unsafe allows direct memory access without exposing the field.

Memory Layout Details

Investigating object offsets revealed that the sole field value of a test class has an offset of 12 bytes on a 64‑bit JVM with compressed ordinary object pointers (Oops) enabled. The object header occupies 8 bytes (MarkWord), followed by a 4‑byte compressed class pointer, then the field data. Disabling compressed Oops with -XX:-UseCompressedOops changes the offset to 16 bytes.

Conclusion

When writing code, always examine the actual implementation of libraries you depend on; hidden pitfalls like the ones in ThreadLocalRandom and Unsafe can cause subtle bugs. Careful study not only avoids problems but also deepens your understanding of Java internals.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaperformanceconcurrencyunsaferandomThreadLocalRandom
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.