Because Java 21 virtual threads are very cheap — per JEP Cafe, they are about 1,000 times faster to launch than a platform thread, and use about 1,000 times less memory than a platform thread — they should never be pooled. As a result, ThreadLocal
variables are unlikely to be useful in applications that use virtual threads for concurrent processing. If each task lives in its own dedicated thread, then each call to ThreadLocal
ends up returning a new value, and it ends up being a factory instead of a pool! So using ThreadLocal
in virtual threads is an anti-pattern. Developers should use another technique for pooling objects, such as structured concurrency, instead.
But what about ThreadLocalRandom
? Is this dedicated class for providing sources of randomness to threads safe to use with virtual threads?
The short answer is: Yes, ThreadLocalRandom
is safe to use with virtual threads. ThreadLocalRandom
is tightly integrated with the Thread
class to ensure that it remains efficient, even for virtual threads. For more information, keep reading.
Understanding ThreadLocal
Internally, ThreadLocal
works by maintaining a mapping from a Thread
to its corresponding value. When code asks a ThreadLocal
for a value, it follows this process:1
L
ook up the current thread.- Check internal map for the current thread’s value.
- If found, then return the value. Otherwise, go to step 4.
- Create a new value, and store it as the current thread’s value in the internal map.
- Return the new value.
If an application creates millions of threads — which is exactly how virtual threads are designed to be used — and each one calls into ThreadLocal
and then does its work and dies, then the ThreadLocal
will accumulate millions of dead objects in its internal map. ThreadLocal
‘s internal map uses weak references, so these dead objects won’t contribute to an OOM, but they also won’t be collected until the JVM starts to experience memory pressure, so before they are collected, the dead objects will trigger extra GC cycles, possibly including full GCs, which are preferably (and typically) avoided.
Hence, using ThreadLocal
with virtual threads is an anti-pattern.
Understanding ThreadLocalRandom
ThreadLocalRandom
, on the other hand, uses fields directly embedded in the Thread
object. When the user asks ThreadLocalRandom
for an instance of Random
, it initializes with the current thread — platform or virtual — and initializes these fields. These fields are all primitives, so there is no risk of object leaks in any case. The returned Random
instance then uses these embedded fields to generate data.
Hence, using ThreadLocalRandom
with virtual threads is perfectly safe. Indeed, it appears tailor-made to work with them.
Conclusions
As a result of this design, every Thread
pays a (small) overhead of about 20 bytes each to ensure that a simple, easy-to-use source of randomess is available, whether they use it or not. And because of its simplicity, it works well for any type of Thread
, be it platform, virtual, or any other Thread
types Java may gain in the future.
That seems a worthy tradeoff.