随机数的分布

时间:2016-07-07 10:10:57

标签: java random uniform-distribution

我有两种代码选择:

选项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,是否对输出分布的一致性有任何保证?

5 个答案:

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

说明:

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

    1. 新的随机种子由nanoTime和“唯一”号码播种。
    2. 该数字不保证唯一,因为其计算不同步。
    3. 至于nanoTime,它保证“至少可以currentTimeMillis
    4. currentTimeMillis不保证任何内容,可以是pretty coarse
    5. 在现实生活中,两次只在old linux systems and Win 98上相同。
  3. 在实践中,每个帖子中的new Random()基本上总是会有不同的种子。

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

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

    1. Randomsomewhat uniform,只有两个值,其结果可为predicted
    2. SecureRandom保证this won't happens。 (即密码强)
    3. 如果在每个帖子中创建一个新的SecureRandom,则不存在种子冲突的风险。
    4. 但目前它的来源是single thread无论如何都没有并行生成。
    5. 对于支持多线程的优秀RNG,请查找Apache Common external help之类的MT
  6.   

    注意:从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的内部状态,以获得更大的性能,然后执行以下所有操作:

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

我的建议是为你的实际应用做基准测试,但我希望选项1是这三个中最慢的。

答案 4 :(得分:0)

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