为什么我在使用 ThreadLocalRandom class 成员时总是得到相同的结果?
Why do I always get the same result when using a ThreadLocalRandom class member?
在我的 Spring 应用程序中,如果我有一个 ThreadLocalRandom
class 成员在构造期间设置:
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();
返回的第一个 int 总是相同的,即使在应用程序重新启动时也是如此。如果我改为修改 getInt()
方法:
public int getInt() {
return ThreadLocalRandom.current().nextInt(1000, 10000);
}
我每次都会获得伪随机值,即使在应用程序重新启动时也是如此(这是我所期望的)。这似乎只发生在 Spring;我在 jshell
和 Replit 中进行了相同的尝试,但没有看到这种行为。对于上下文,生成随机数的 Spring bean 是自动装配的。
老实说,我已经从那部分代码中完全删除了随机数生成,而是使用注入的依赖项,因此我可以对其进行单元测试。
这个问题是为了让我更好地了解正在发生的事情。据我所知,在构建期间设置 class 成员应该在应用程序重新启动时提供不同的值。
将构造函数中 ThreadLocalRandom.current()
的结果存储在 class 的实例变量中是一个坏主意。事实上完全超过了 ThreadLocalRandom
的目的,然后你最好使用常规 Random
或 SecureRandom
.
话虽这么说。这与 Spring 无关,但您将 ThreadLocalRandom.current()
存储在实例变量中的事实。以下程序将产生相同的结果,但它不使用 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)) {
}
}
发生的事情是 Random
在 main
线程上初始化,随机使用的种子等被存储 in-memory 用于 main
线程。现在您启动一个新线程,它会尝试找到该线程的种子,并且存在 none,因此它将始终使用 0 作为种子,因为没有种子绑定到该线程。
这都是因为将 ThreadLocalRandom
从 main 存储在一个实例变量中。 ThreadLocalRandom
(顾名思义)使用线程绑定种子等来生成随机数。这就是为什么您应该始终使用 ThreadLocalRandom.current()
而不是将其结果存储在变量中的原因。
在我的 Spring 应用程序中,如果我有一个 ThreadLocalRandom
class 成员在构造期间设置:
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();
返回的第一个 int 总是相同的,即使在应用程序重新启动时也是如此。如果我改为修改 getInt()
方法:
public int getInt() {
return ThreadLocalRandom.current().nextInt(1000, 10000);
}
我每次都会获得伪随机值,即使在应用程序重新启动时也是如此(这是我所期望的)。这似乎只发生在 Spring;我在 jshell
和 Replit 中进行了相同的尝试,但没有看到这种行为。对于上下文,生成随机数的 Spring bean 是自动装配的。
老实说,我已经从那部分代码中完全删除了随机数生成,而是使用注入的依赖项,因此我可以对其进行单元测试。
这个问题是为了让我更好地了解正在发生的事情。据我所知,在构建期间设置 class 成员应该在应用程序重新启动时提供不同的值。
将构造函数中 ThreadLocalRandom.current()
的结果存储在 class 的实例变量中是一个坏主意。事实上完全超过了 ThreadLocalRandom
的目的,然后你最好使用常规 Random
或 SecureRandom
.
话虽这么说。这与 Spring 无关,但您将 ThreadLocalRandom.current()
存储在实例变量中的事实。以下程序将产生相同的结果,但它不使用 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)) {
}
}
发生的事情是 Random
在 main
线程上初始化,随机使用的种子等被存储 in-memory 用于 main
线程。现在您启动一个新线程,它会尝试找到该线程的种子,并且存在 none,因此它将始终使用 0 作为种子,因为没有种子绑定到该线程。
这都是因为将 ThreadLocalRandom
从 main 存储在一个实例变量中。 ThreadLocalRandom
(顾名思义)使用线程绑定种子等来生成随机数。这就是为什么您应该始终使用 ThreadLocalRandom.current()
而不是将其结果存储在变量中的原因。