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 `ThreadLocalRandom.nextInt()` internally. And the
latter is fixed in this PR.
-------------
Commit messages:
- 8372134: ThreadLocalRandom no longer overrides nextGaussian
Changes: https://git.openjdk.org/jdk/pull/28483/files
Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=28483&range=00
Issue: https://bugs.openjdk.org/browse/JDK-8372134
Stats: 9 lines in 2 files changed: 8 ins; 1 del; 0 mod
Patch: https://git.openjdk.org/jdk/pull/28483.diff
Fetch: git fetch https://git.openjdk.org/jdk.git pull/28483/head:pull/28483
PR: https://git.openjdk.org/jdk/pull/28483