我有两种代码选择:
选项1
int myFunc() {
return new Random().nextInt();
}
或者:
选项2
private static final Random random = new Random();
int myFunc() {
return random.nextInt();
}
我知道option 2
更为惯用。我想知道option 1
的有效性。
在option 1
中,我将只使用给定种子生成的第一个数字。在option 2
中,我选择种子并使用该种子生成n
个数字。 IIUC对随机性的保证就是这个用例。
因此,我的问题是,如果我多次致电option 1
,是否对输出分布的一致性有任何保证?
答案 0 :(得分:9)
快速代码:
// For occasional tasks that just need an average quality random number
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute( () -> {
ThreadLocalRandom.current().nextInt(); // Fast and unique!
} );
// For SecureRandom, high quality random number
final Random r = new SecureRandom();
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute( () -> {
r.nextInt(); // sun.security.provider.NativePRNG uses singleton. Can't dodge contention.
} );
// Apache Common Math - Mersenne Twister - decent and non-singleton
int cpu = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool( cpu );
Map<Thread, RandomGenerator> random = new WeakHashMap<>( cpu, 1.0f );
executor.execute( ()-> {
RandomGenerator r;
synchronized ( random ) { // Get or create generator.
r = random.get( Thread.currentThread() );
if ( r == null ) random.put( Thread.currentThread(), r = new MersenneTwister() );
}
r.nextInt( 1000 );
} );
说明:
Random
会产生相同的数字。
理论上,每个帖子中的new Random()
都不能保证不同的种子。
在实践中,每个帖子中的new Random()
基本上总是会有不同的种子。
zapl suggested ThreadLocalRandom.current().nextInt()
。好主意。
Random
,但它也是linear congruential generator。SecureRandom
静态播种,可生成质量更好的随机数。“统一分发”只是randomness tests的一小部分。
Random
为somewhat uniform,只有两个值,其结果可为predicted。SecureRandom
保证this won't happens。 (即密码强)SecureRandom
,则不存在种子冲突的风险。注意:从Java 8源代码推导出的实现细节。 未来的Java版本可能会改变;例如,
ThreadLocalRandom
正在使用sun.misc.Unsafe
来存储种子, Java 9中的may be removed强制ThreadLocalRandom在没有争用的情况下找到一种新的工作方式。
答案 1 :(得分:3)
我真正的问题是选项1在数学上是否有效。
让我们从选项2开始。java.util.Random
使用的随机数生成器在javadoc中指定如下:
该类使用48位种子,使用线性同余公式进行修改。 (参见Donald Knuth,“计算机程序设计的艺术”,第2卷,第3.2.1节。)
并且各种方法'javadocs中有更具体的细节。
但重点是我们使用的是由线性同余公式生成的序列,这些公式具有很大程度的自相关性......这可能会有问题。
现在使用选项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() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
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()
可能调用涉及系统调用。那是相对昂贵的。
所以简短的回答是它是特定于Java版本的选项#1和选项#2从数学角度产生质量更好的“随机”数字。
在这两种情况下,数字的分布在足够大的样本大小上是均匀的,但我不确定当过程是确定性时谈论概率分布是有意义的。
然而,这两种方法都不适合作为“加密强度”随机数发生器。
答案 2 :(得分:1)
否强>
对选项1将生成的数字分布的属性没有任何保证。正如在其他答案中已经明确的那样,java.util.Random
的构造函数的实现取决于系统时间。因此,为了保证您使用选项1获得的数字分布的属性,您需要能够保证您的程序调用所产生的数字的分配,以便在任何系统上获得系统时间。程序将运行的平台。
但是,对于选项2,可以对一次执行程序期间产生的数字分布进行数学保证。使用线性同余生成器(java.util.Random
使用的伪随机数生成算法),随机性的某些属性不如其他算法好,但保证分布相对均匀。
这并不一定意味着选项1无法满足您的目的。这取决于你在做什么。
答案 3 :(得分:0)
Java使用System.nanoTime()
和顺序计数器初始化随机种子。这在某种程度上保证了每次调用时种子都会有所不同,尽管我会避免将其称为加密安全。
从性能的角度来看 - 你真的希望在选项1中锁定Random的内部状态,以获得更大的性能,然后执行以下所有操作:
我的建议是为你的实际应用做基准测试,但我希望选项1是这三个中最慢的。
答案 4 :(得分:0)
根据我的经验,通过使用类似“Messerne Twister”生成器(see in Apache Commons)之类的东西,可以获得良好分布和性能之间的最佳平衡。有关更加优雅的解决方案,请参阅this。