我的真正问题是选项1是否在数学上有效。
我们从选项2开始。 java.util.Random使用的随机数生成器在javadoc中规定如下:
该类使用一个48位种子,该种子使用线性同余公式进行修改。(请参见Donald Knuth,《计算机编程艺术》第2卷第3.2.1节。)
各种方法的javadoc中还有更具体的细节。
但是关键是,我们正在使用由线性同余公式生成的序列,而这样的公式具有相当程度的自相关性……这可能会有问题。
现在,对于选项1,您将使用一个不同的Random实例,并每次使用一个新的种子,并应用一轮LC公式。因此,您将获得一系列数字,这些数字很可能与种子自相关。但是,种子的生成方式取决于Java版本。
Java 6执行以下操作:
public Random() { this(++seedUniquifier + System.nanoTime()); }
private static volatile long seedUniquifier = 8682522807148012L;
如果你按固定间隔创建Random
实例,种子很可能会非常接近,因此选项#1生成的随机数序列容易自相关。
相比之下,Java 7和8做到了这一点:
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
private static final AtomicLong seedUniquifier
= new AtomicLong(8682522807148012L);
上述方法生成的种子序列可能更接近(真正的)随机性,这使得选项#1比选项#2更优秀。
Java 6到8中选项#1的缺点是System.nanoTime()调用可能涉及系统调用。 这相对昂贵。
简短的答案是,从数学角度来看,选项#1和选项#2哪个产生更好的“随机”数字取决于Java版本。
在两种情况下,数字的分布将在足够大的样本大小上均匀,尽管当过程是确定性的时,谈论概率分布可能没有意义。
然而,无论哪种方法都不适合作为“加密强度”随机数生成器。
ThreadLocalRandom.current().nextInt()
。另外,您关于同时生成随机数的说法是错误的,详情请参考https://dev59.com/lHnZa4cB1Zd3GeqPoErj#20060801,它不仅使用时间进行初始化。 - zapl