On Tue, 25 Nov 2025 00:13:03 GMT, James Yuzawa <[email protected]> wrote:

> I have noticed in Java 25 (and earlier versions) that calling 
> `ThreadLocalRandom.current().nextGaussian()` uses the Random.nextGaussian() 
> implementation, which is synchronized. Under heavy load in a multithreaded 
> environment, I was detecting lock contention.
> 
> Here is a very simple reproducer:
> 
> ThreadLocalRandom r = ThreadLocalRandom.current();
> // step into this call using a debugger
> r.nextGaussian();
> 
> 
> It dispatches to the synchronized Random implementation, since 
> ThreadLocalRandom extends Random, thus the default implementation (not 
> synchronizing) on RandomGenerator is not used.
> 
> Sketch:
> 
> public interface RandomGenerator {
>       default double nextGaussian() {
>               // remove TAOCP comment since it is out of date, and this uses 
> the ziggurat algorithm instead
>               return RandomSupport.computeNextGaussian(this);
>       }
> }
> 
> public class Random implements RandomGenerator {
>       @Override
>       public synchronized double nextGaussian() {
>               // synchronized version ...
>       }
> }
> 
> public class ThreadLocalRandom extends Random {
> 
>       // ADD THIS
>       @Override
>       public double nextGaussian() {
>               return RandomSupport.computeNextGaussian(this);
>       }
> }
> 
> 
> A comment on ThreadLocalRandom states "This implementation of 
> ThreadLocalRandom overrides the definition of the nextGaussian() method in 
> the class Random, and instead uses the ziggurat-based algorithm that is the 
> default for the RandomGenerator interface.” However, there is none such 
> override happening. It appears that prior to 
> a0ec2cb289463969509fe508836e3faf789f46d8 the nextGaussian implementation was 
> non-locking since it used proper ThreadLocals.
> 
> I conducted an audit of all of the RandomGenerator and Random methods to see 
> if there are any others:
> 
> 
> Set<String> tlrMethods = new HashSet<>();
> for (Method method : 
> java.util.concurrent.ThreadLocalRandom.class.getDeclaredMethods()) {
>       int mod = method.getModifiers();
>       if (!Modifier.isStatic(mod) && Modifier.isPublic(mod)) {
>               String desc =
>                               method.getReturnType() + " " + method.getName() 
> + " " + Arrays.toString(method.getParameters());
>               tlrMethods.add(desc);
>       }
> }
> for (Method method : java.util.Random.class.getDeclaredMethods()) {
>       int mod = method.getModifiers();
>       if (!Modifier.isStatic(mod) && Modifier.isPublic(mod)) {
>               String desc =
>                               method.getReturnType() + " " + method.getName() 
> + " " + Arrays.toString(method.getParameters());
>               if (!tlrMethods.contains(desc)) {
>                       System.out.println(desc);
>               }
>       }
> }
> 
> 
> That prints
> 
> void nextBytes [byte[] arg0]
> double nextGaussian []
> 
> 
> The former safely calls `Thread...

src/java.base/share/classes/java/util/concurrent/ThreadLocalRandom.java line 
503:

> 501: 
> 502:     /**
> 503:      * {@inheritDoc}

Inheriting the doc from `Random` doesn't seem correct in this case.

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/28483#discussion_r2560363811

Reply via email to