Home > OS >  Why do I always get the same result when using a ThreadLocalRandom class member?
Why do I always get the same result when using a ThreadLocalRandom class member?

Time:03-03

In my Spring app, if I have a ThreadLocalRandom class member that is set during construction:

public class RNG {
  private ThreadLocalRandom rng;
  
  public RNG() {
    rng = ThreadLocalRandom.current();
  }
  
  public int getInt() {
    return rng.nextInt(1000, 10000);
  }
}

// some other class
RNG a = new RNG();
a.getInt();

The first int that's returned is always the same, even across restarts of the app. If I instead modify the getInt() method:

public int getInt() {
  return ThreadLocalRandom.current().nextInt(1000, 10000);
}

I'll get psuedo-random values every time, even across restarts of the app (which is what I expect). This seems to only happen in Spring; I've tried the same in jshell and Replit and don't see this behavior. For context, the Spring bean that generates the random number is autowired.

To be completely honest, I've removed random number generation from that part of the code altogether, and use an injected dependency instead, so I can unit test it.

This question is for me to get a better understanding of what is happening. As far as I'm aware, setting the class member during construction should provide a different value across restarts of the app.

CodePudding user response:

Storing the result of ThreadLocalRandom.current() in the constructor in an instance variable of a class is a bad idea. In fact beats the purpose of ThreadLocalRandom completely and you are then better of with an regular Random or SecureRandom.

That being said. This has nothing to do with Spring but the fact that you are storing the ThreadLocalRandom.current() result in an instance variable. The following program will yield the same result and it doesn't use Spring.

public static void main(String[] args) throws Exception {
    var rnd = ThreadLocalRandom.current();

    System.out.println("Starting with RND "   rnd.getClass().getName()   " - "   rnd.hashCode());

    var executor = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 50; i  ) {
        executor.submit(() -> System.out.println("Value: "   rnd.nextInt(1000, 10000)));
    }

    System.out.println("Waiting to finish!");

    executor.shutdown();
    while (!executor.awaitTermination(50, TimeUnit.MILLISECONDS)) {

    }
}

What happens is that the Random is initialized on the main thread and the seed etc. used by the random is stored in-memory for the main thread. Now you launch a new thread, it tries to find the seed for that thread and there is none, so it will seed always with 0, because no seed bound to that thread.

This is all due to storing the ThreadLocalRandom from main in a instance variable. The ThreadLocalRandom (as the name implies) uses thread bound seeds etc. to generate a random number. Which is why you should always use ThreadLocalRandom.current() and not store the result of that in a variable.

  • Related