In video [1:23] Jose Paumard talks about why using ThreadLocal is evil; especially, if the code creates countless (virtual) threads.
public static void threadLocalRandomTest() {
enum TestType {
useNewThreadLocalRandom, useNewRandom,
ReUseThreadLocal, ReUseRandom,
ReUseGlobalRandom,
customThreadLocalRandom, customRandom
}
var testType = TestType.useNewThreadLocalRandom;
var rg = new Random();
IntStream.range(0, 1_000_000).forEach(i -> {
if (testType == TestType.useNewThreadLocalRandom) {//63mb->60mb
IntStream.range(0, 100).forEach(j -> {
ThreadLocalRandom.current().nextFloat(1);
});
}
if (testType == TestType.useNewRandom) {//326mb->323mb
IntStream.range(0, 100).forEach(j -> {
new Random().nextFloat(1);
});
}
if (testType == TestType.ReUseThreadLocal) {//65mb->61mb
var r = ThreadLocalRandom.current();
IntStream.range(0, 100).forEach(j -> {
r.nextFloat(1);
});
}
if (testType == TestType.ReUseRandom) {//111mb->108mb
var r = new Random();
IntStream.range(0, 100).forEach(j -> {
r.nextFloat(1);
});
}
if (testType == TestType.ReUseGlobalRandom) {//64mb->61mb
IntStream.range(0, 100).forEach(j -> {
rg.nextFloat(1);
});
}
});
}
Jose Paumard's warning about ThreadLocal being "evil" with virtual threads refers to arbitrary ThreadLocal usage, which would cause memory bloat with millions of virtual threads. But ThreadLocalRandom is safe. Each virtual thread gets one tiny ThreadLocalRandom instance (just 16 bytes of state), and it avoids contention (unlike shared Random), and has lower overhead than creating new Random instances repeatedly (as your tests show: 60MB vs 323MB).
Using new Random() in tight loops (like your useNewRandom test) creates massive GC pressure. Even ReUseRandom (108MB) underperforms vs ThreadLocalRandom (60MB) because it serializes access via synchronization.
Keep using ThreadLocalRandom.current(). It's the correct choice for virtual threads. Your tests accidentally prove this: the lowest memory usage comes from reusing ThreadLocalRandom (60MB), while new Random() is 5x worse.
void generateRandoms() {
// Reuse the thread-local instance (no per-call overhead)
var random = ThreadLocalRandom.current();
IntStream.range(0, 100).forEach(j ->
random.nextFloat()
);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With