7

我有两种代码选择:

选项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多次调用,是否可以保证输出分布的均匀性?

4

5 回答 5

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 );
} );

解释:

  1. 两个Random相同的种子将产生相同的数字。
    1. 因此,我们将关注是否可以保证不同的种子。
  2. 理论上,new Random()在每个线程中不保证不同的种子。

    1. new Random 由nanoTime和“唯一”数字播种。
    2. 该数字不能保证唯一,因为它的计算不同步。
    3. 至于 nanoTime,它保证“至少和currentTimeMillis一样好”
    4. currentTimeMillis 不保证任何内容,而且可能非常 粗略
    5. 在现实生活中,这两个时间仅在旧的 linux 系统和 Win 98上是相同的。
  3. 在实践中,new Random()在每个线程中基本上总是得到不同的种子。

    1. 创建线程是昂贵的。我的每 50,000ns 产生 1 个。这并不
    2. 50µs 远高于 nanoTime 常见的高达几十纳秒的粒度。
    3. 唯一数计算(1.2)也很快,所以得到相同的数字是非常罕见的。
    4. 使用Executors创建线程池来避免沉重的新线程开销。
  4. zapl建议 ThreadLocalRandom.current().nextInt()。很好的主意。

    1. 它不创建新Random的,但它也是一个线性同余生成器
    2. 它为每个调用线程生成一个新的随机数作为该线程的种子。
    3. 它被构建为在多线程中非常快。(见下面的注释。)
    4. 它由 静态播种SecureRandom,产生质量更好的随机数。
  5. “均匀分布”只是随机性 测试的一小部分。

    1. Random有点统一,只需给定两个值就可以预测其结果。
    2. SecureRandom保证这不会发生。(即密码学强)
    3. 如果您SecureRandom在每个线程中创建一个新的,则没有种子冲突的风险。
    4. 但目前它的来源无论如何都是单线程的,没有并行生成。
    5. 对于支持多线程的良好 RNG,请查找Apache Common 的MT等外部帮助

注意:从 Java 8 源代码推导出的实现细节。未来的 Java 版本可能会发生变化;例如,ThreadLocalRandom用于sun.misc.Unsafe存储种子,它可能会在 Java 9 中被删除,从而迫使 ThreadLocalRandom 找到一种新的工作方式而不会发生争用。

于 2016-07-11T10:41:06.913 回答
4

我真正的问题是选项 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()可能的调用涉及系统调用。那是相对昂贵的。


所以简短的回答是,从数学的角度来看,选项#1 和选项#2 中的哪一个产生更好质量的“随机”数字是 Java 版本特定的。

在这两种情况下,数字的分布在足够大的样本量上都是均匀的,尽管我不确定在过程是确定性的情况下讨论概率分布是否有意义。

然而,这两种方法都不适合作为“加密强度”随机数生成器。

于 2016-07-13T07:53:17.303 回答
1

不。

不能保证选项 1 将产生的数字分布的属性。正如其他答案中已明确指出的那样,构造函数的实现java.util.Random取决于系统时间。因此,为了保证您使用选项 1 获得的数字分布的属性,您需要能够保证您的程序调用所产生的数字分布,以便在任何程序运行的平台。

但是,对于选项 2,可以对在一次程序执行期间将产生的数字的分布做出数学保证。使用线性同余生成器(由 使用的伪随机数生成算法java.util.Random)随机性的一些属性不如其他算法好,但可以保证分布相对均匀。

这并不一定意味着选项 1 不能满足您的目的。这取决于你在做什么。

于 2016-07-16T15:07:07.013 回答
0

以我的经验,良好的分布和性能之间的最佳平衡是通过使用类似“Messerne Twister”生成器 (参见 Apache Commons)来实现的。如需更高级的解决方案,请参阅

于 2016-07-15T20:21:58.907 回答
0

System.nanoTime()Java 用一个顺序计数器初始化随机种子。这在一定程度上保证了每次调用的种子都是不同的,尽管我不会将其称为密码安全。

从性能的角度来看 - 您是否真的希望在选项 1 中锁定 Random 的内部状态会对性能产生更大的影响,然后是以下所有内容:

  • 访问和递增 volatile long
  • 获取当前系统时间(这是相当昂贵的
  • 动态分配
  • 垃圾收集的另一个对象

我的建议是对您的实际应用程序进行基准测试以找出答案,但我希望选项 1 是所有三个中最慢的。

于 2016-07-07T10:27:08.073 回答