30

For some reason I used to think that java.util.Random is thread-unsafe, a-la HashMap or BitSet, and Math.random() is implemented either as wrapping access to Random with synchronized block, or ThreadLocalRandom.current().nextDouble().

Actually it turns out that java.util.Random is thread-safe (via atomics). Hence the takeaway: even if I need some random input in a single thread, it makes sense to use ThreadLocalRandom, because there isn't atomic reads and writes inside, compiled as locked instructions and emitting memory barriers.

Moreover, since Java 8, ThreadLocalRandom is essentially a singleton, its state is kept in some fields of java.lang.Thread class. Therefore method ThreadLocalRandom.current() is not an access to ThreadLocalMap, but just a static field read, i. e. very cheap.

I have two questions:

  1. From Computer Science point of view, is the output of several linear congruential random generators (initialized the way ThreadLocalRandoms are) is same "random" as an output of the single linear congruential random generator (the java.util.Random instance)?

  2. If answer to the first question is Yes, is there any reason to write the construction new Random() (without seed) instead of ThreadLocalRandom.current(), ever?

Update. I supposed that calls like ThreadLocalRandom.current().ints().parallel().collect(...) could be incorrect, because Thread's random generator state might be not initialized in ForkJoinPool worker threads, but appears that ThreadLocalRandom overrides methods ints(), longs() and doubles(), making the above construction correct.

Community
  • 1
  • 1
leventov
  • 14,760
  • 11
  • 69
  • 98
  • 3
    You can't write http://stackoverflow.com/questions/15182496/why-does-this-code-using-random-strings-print-hello-world with ThreadLocalRandom. – user253751 Apr 17 '15 at 12:06
  • @immibis well, it means the only case -- when I need to reproduce some sequence of random numbers – leventov Apr 17 '15 at 12:09
  • @immibis however, the question is about the specific construction `new Random()`, i. e. without the seed. The question is open. – leventov Apr 17 '15 at 12:11
  • If you do need a seed [`SplittableRandom`](https://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html) is probably better than `Random` (for reasons listed in the javadoc and it has more methods for random numbers in ranges). – Alex - GlassEditor.com Feb 18 '16 at 17:59

1 Answers1

3

1...

It depends on implementation but for Java it will be the same not as bad because Java has a static unique seed atomic long that is manipulated everytime a Random is created. However I would not be surprised in other languages or implementations this is not the case and they might just use the system time (Java uses the system time as well but uses the unique seed in combination). That is on some systems you could get the same seed for multiple threads.

After further examination and some actual testing (albeit brittle testing) it appears I might have been wrong before in that it is actually worse to use many (I'm talking 100k) Random number generators at the same time (even though they are different instances). I'm not entirely sure if its seed collision or just the fact that the actual global seed incrementing becomes predictable. Of course this could just be my testing harness or methodology.

According to wikipedia:

Random number generators, particularly for parallel computers, should not be trusted.[12] It is strongly recommended to check the results of simulation with more than one RNG to check if bias is introduced. Among the recommended generators for use on a parallel computer include combined linear congruential generators using sequence splitting and lagged Fibonacci generators using independent sequences.

So in theory it is supposed to be better since ThreadLocalRandom would create independent sequences so maybe my testing is flawed.

This is of course based on pseudo random.

Physical randomness or a secure random generator based on actually entropy might result in differences (ie more/less entropy) but I'm not an expert and I don't have access to one.

2...

I can't come up with a particular use case but one might be that you use an ExecutorService that constantly creates and disposes of threads (assume they don't have control of this) but never many at once (ie max 2 concurrent threads). You might find the ThreadLocalRandom to be more expensive instead of creating a single shared Random.

Another reason and probably better reason given your comments is that you might want to reset the seed for all processes. If you have a game that uses threads (not many do but lets pretend) you might want to global reset the seed for testing purposes which is far easier with an AtomicReference to a Random than trying to message pass to all the running threads.

Another reason you might not want to use ThreadLocalRandom are platform reasons. Some platforms have specific requirements on thread creation and thus threadlocal creation. Thus to address "you have a bigger problem, than randoms" check out Google Apps where:

A Java application can create a new thread, but there are some restrictions on how to do it. These threads can't "outlive" the request that creates them. (On a backend server, an application can spawn a background thread, a thread that can "outlive" the request that creates it.)

And to address your additional comment of why would you use an ExecutorService that can't reuse threads:

or use the factory object returned by com.google.appengine.api.ThreadManager.currentRequestThreadFactory() with an ExecutorService (e.g., call Executors.newCachedThreadPool(factory)).

ie a ThreadPool that doesn't necessarily reuse threads.

Adam Gent
  • 47,843
  • 23
  • 153
  • 203
  • 1. The question is specifically about Java. If it is perfectly random, why `Math.random()` is not `ThreadLocalRandom.current().nextDouble()`? – leventov Apr 17 '15 at 17:09
  • 2
    2. Creation and disposal of a thread is by orders of magnitude more expensive, than initializing random generator inside this Thread. So, if your ExecutorService constantly creates and drops threads (through the main purpose of ExecutorServices -- to *reuse* threads), you have a bigger problem, than randoms... – leventov Apr 17 '15 at 17:11
  • 2
    @leventov of course I agree with your points. I don't think you understand that ThreadLocalRandom is younger than Random and that not all runtimes like having thread local variables hanging around. ThreadLocal variables were put into Java version 1.2 where as Random is version 1.0. Also you may not have control over the ExecutorService if your on a different platform... There are some platform where you do not control thread creation and threadlocal variables maybe removed on every dispatch. Not everything is Java SE enterprise! – Adam Gent Apr 17 '15 at 17:49
  • 1
    @leventov: the behavior of `Math.random()` has been specified within its documentation. Changing it to use `ThreadLocalRandom` would violate its specification. – Holger Apr 20 '15 at 08:28
  • 1
    @Holger Specification is not a kind of a bible that having been written once is the primary source for all implementations forever. There are a lot of examples when the specification has changed to become compiant with a new (optimized) implementation, e.g. Arrays.sort, class initialization locks etc. – apangin Apr 26 '15 at 14:47
  • @apangin: you are confusing specification with implementation notes. The *specification* of `Arrays.sort` never changed, as the specification never mandated a particular algorithm or error treatment. That’s different to `Math.random`. Usually, specifications are given in a way that leaves implementations a certain degree of freedom, however, sometimes, like in this case, a specification is very detailed about the behavior. – Holger Apr 27 '15 at 07:55
  • 1
    @Holger Java Language, VM and API Specifications cannot be changed in minor JDK updates (unless there is a significant bug). However they ARE changed in major releases. For example, class loading and initialization process was re-worked in Java 7, and both JVMS and API were updated to reflect those changes. I mean current `Math.random` specification is not a strong reason to stay away from further optimizations. Probably it is a good time to change this for JDK 9. – apangin Apr 27 '15 at 10:48