【问题标题】:Distribution of Random Numbers随机数的分布
【发布时间】:2016-11-09 14:47:36
【问题描述】:

我有两种代码选择:

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

【问题讨论】:

  • 选择选项 3:ThreadLocalRandom.current().nextInt()。同时你对随机数也是错误的,stackoverflow.com/a/20060801/995891 这不仅仅是使用时间来初始化。
  • 谢谢,我不知道这一点。我会更新问题

标签: java random uniform-distribution


【解决方案1】:

快速代码:

// 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. 新的 Random 由 nanoTime 和“唯一”编号播种。
    2. 该数字不能保证唯一,因为它的计算不同步。
    3. 至于nanoTime,它保证“至少不亚于currentTimeMillis”
    4. currentTimeMillis 不做任何保证,可以是prettycoarse。
    5. 在现实生活中,这两个时间仅在old linux systems and Win 98上是相同的。
  3. 实际上,每个线程中的new Random() 基本上总是得到不同的种子。

    1. 创建线程很昂贵。我的每 50,000ns 产生 1 个。那是not slow。
    2. 50µs 远高于 nanoTime 高达a few ten ns 的常见粒度。
    3. 唯一数计算 (1.2) 也很快,因此得到相同的数字非常罕见。
    4. 使用Executors 创建thread pool 以避免沉重的新线程开销。
  4. zaplsuggestedThreadLocalRandom.current().nextInt()。好主意。

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

    1. Random 是 somewhat uniform,它的结果可以是 predicted,只要给定两个值。
    2. SecureRandom 保证 this won't happens。 (即加密强度高)
    3. 如果您在每个线程中创建一个新的SecureRandom,则没有种子冲突的风险。
    4. 但目前它的来源是single thread,反正没有并行生成。
    5. 如需支持多线程的优秀 RNG,请查找 external help,如 Apache Common 的 MT。

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

【讨论】:

  • 谢谢。您说“每个新线程中的 new Random() 都不能保证均匀分布。”你能解释更多吗。为什么时钟的性能与随机性有关?正如 zapl 所说,即使在完全相同的时间调用两次,Random 也会给出不同的种子。为什么启动线程的时间会对分布的随机性产生负面影响?能给个出处吗?
  • @BenjyKessler 从理论上讲,从 Java 8 u92 开始,在完美的并行执行中同时创建两个随机数应该会产生两个具有相同种子的随机数。但是“完全相同的时间”和“完美的并行执行”是很难的。我想说的是因为 Thread 需要相对较长的时间来创建,仅 nanoTime 偏差就可以确保您的 Random 在实践中获得不同的种子(第 2 点),即使不能保证(第 1 点)。
  • 对不起,我想我的问题不清楚。 RNG 的工作方式是将一个随机数转换为另一个随机数。因此,从一个“随机”数字开始,您可以生成任意数量的“随机”数字。关键是你从一个随机数开始,输出的随机性取决于种子的随机性。就我而言,我使用的是不同的模型。我不是从一个“随机”种子开始,而是从n 高度相关的种子开始。我的问题是,我是否仍然可以保证一致性(一致性,而不是随机性)。
【解决方案2】:

我真正的问题是选项 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 版本特定的。

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

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

【讨论】:

  • 谢谢,这很有趣。您提到 System.nanoTime 很昂贵。如果只使用 seedUniquifier 作为种子而不是调用默认构造函数,会发生什么?我可以在启动时计算一次 seedUniquifier() ^ System.nanoTime() ,然后将其用作我的初始种子,以避免每次运行都获得相同的序列。结果是否会自相关?
  • “你提到 System.nanoTime 很贵。” - 相对很贵。
  • “我可以在启动时计算一次seedUniquifier() ^ System.nanoTime(),然后将其用作我的初始种子,以避免每次运行都获得相同的序列。结果会是自相关的吗? " - 是的。我想。但是你可以测试一下。谷歌为一些线索“测试自相关”。
【解决方案3】:

没有。

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

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

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

【讨论】:

【解决方案4】:

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

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

  • 访问和递增 volatile long
  • 获取当前系统时间 (which is quite expensive)
  • 动态分配
  • 另一个垃圾回收对象

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

【讨论】:

  • 谢谢。我对了解“加密安全”方面更感兴趣。效率只是我为什么要使用选项 1 的一个理由。我真正的问题是选项 1 在数学上是否有效。
  • docs.oracle.com/javase/7/docs/api/java/util/Random.html "java.util.Random 的实例是线程安全的。但是,跨线程并发使用同一个 java.util.Random 实例可能会遇到争用,从而导致性能下降。"和“java.util.Random 的实例在密码学上不安全。考虑改为使用 SecureRandom 来获取密码学安全的伪随机数生成器,以供安全敏感的应用程序使用。”
  • 我想我的问题对于SecureRandom 将保持不变。每次创建一个新的实例并从给定的种子只生成一个实例,仍然是安全的吗?或者它是否与种子的选择(调用函数的时间)如此相关,以至于失去了随机性。
【解决方案5】:

根据我的经验,通过使用“Messerne Twister”生成器(see in Apache Commons) 之类的东西可以在良好的分布和性能之间取得最佳平衡。如需更高级的解决方案,请参阅this。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-16
    • 2012-03-10
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    相关资源
    最近更新 更多