JavaRush /Java 博客 /Random-ZH /实践中的概率论或者您了解随机吗
Viacheslav
第 3 级

实践中的概率论或者您了解随机吗

已在 Random-ZH 群组中发布
实践中的概率论或者您了解随机 - 1

介绍

世界上有许多科学研究概率论。科学由不同的部分组成。例如,在数学中,有一个单独的部分专门研究随机事件、数量等。但科学并没有被轻视。在这种情况下,当人们试图了解玩机会游戏时掷骰子的模式时,概率论开始成形。如果你仔细观察,我们周围有很多看似随机的事情。但一切随机的事情并不完全随机。但稍后会详细介绍。从 JDK 的第一个版本开始,Java 编程语言也支持随机数。Java 中的随机数可以通过java.util.Random类来使用。为了进行测试,我们将使用tutorialspoint java在线编译器下面是一个使用Random来模拟投掷“骰子”(俄语中的立方体) 的原始示例:
import java.util.Random;

public class HelloWorld{
    public static void main(String []args){
        Random rnd = new Random();
        int number = rnd.nextInt(6) + 1;
        System.out.println("Random number: " + number);
    }
}
看起来对Random的描述到此就结束了,但事实并非如此简单。我们打开Java API中java.util.Random类的描述。在这里我们看到了有趣的事情。Random类使用伪随机数。为何如此?原来随机数并没有那么随机?
实践中的概率论或者您了解随机 - 2

伪随机性 java.util.Random

java.util.Random类的文档表示,如果使用相同的种子参数创建Random实例并对实例执行相同的操作序列,则它们将返回相同的数字序列。如果我们仔细观察,我们可以看到Random实际上有一个构造函数,它以一些long值作为种子:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
这个例子将返回true因为 两个实例的种子是相同的。该怎么办?默认构造函数部分解决了这个问题。以下是Random构造函数的内容示例:
public Random() {
	this(seedUniquifier() ^ System.nanoTime());
}
默认构造函数使用按位异运算。并使用代表当前时间的long一些种子:
private static long seedUniquifier() {
	for (;;) {
		long current = seedUniquifier.get();
		long next = current * 181783497276652981L;
		if (seedUniquifier.compareAndSet(current, next))
			return next;
	}
}
这里另一个有趣的事情是,每次调用SeedUniquifier getter 方法都会更改SeedUniquifier 的值。也就是说,该类被设计为尽可能有效地选择随机数。然而,正如文档所说,它们“在加密上并不安全”。也就是说,对于某些用于加密目的(密码生成等)的用途来说,它是不适合的,因为 使用正确方法预测序列。互联网上有关于此主题的示例,例如:“ Predicting the next Math.random() in Java ”。或者例如这里的源代码:“ Vulnerability Weak Crypto ”。java.util.Random (随机数生成器)有一个特定的“捷径”,通过 Math.random 执行的调用的缩短版本:
public static void main(String []args){
	int random_number = 1 + (int) (Math.random() * 6);
	System.out.println("Value: " + random_number);
}
但如果你仔细观察,会发现里面有同样的随机数:
public static double random() {
	return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
	static final Random randomNumberGenerator = new Random();
}
JavaDoc 建议使用SecureRandom类作为“加密安全伪随机数生成器”。
实践中的概率论或者您了解随机 - 3

安全随机 Java

SecureRandom类是java.util.Random的子类,位于java.security包中。这两个类的比较可以阅读文章“ java.util.Random 和 java.security.SecureRandom 之间的差异”。为什么这个 SecureRandom 这么好?事实上,对他来说,随机数的来源就是“核心熵池”这样一个听起来很神奇的东西。这既是优点也是缺点。您可以在文章“ java.security.SecureRandom 的危险”中了解其缺点。简而言之,Linux 有一个内核随机数生成器(RNG)。RNG 根据熵池中的数据生成随机数,熵池根据系统中的随机事件填充,例如键盘和磁盘计时、鼠标移动、中断和网络流量。有关熵池的更多信息,请参阅材料“ Linux 中的随机数 (RNG) 或如何“填充”/dev/random 和 /dev/urandom ”。在 Windows 系统上,使用 SHA1PRNG,在 sun.security.provider.SecureRandom 中实现。随着 Java 的发展,SecureRandom 也发生了变化,值得阅读评论“截至 2016 年 4 月的 Java SecureRandom 更新”以了解完整情况。
实践中的概率论或者您了解随机 - 4

多线程或者像凯撒一样

如果您查看Random类的代码,似乎没有任何迹象表明有问题。方法未标记为同步。但有一个 BUT:当在多个线程中使用默认构造函数创建Random时,我们将在它们之间共享相同的实例种子,通过该种子来创建Random。而且当收到新的随机数时,实例的内部AtomicLong也会发生变化。一方面,从逻辑的角度来看,这并没有什么问题,因为…… 使用AtomicLong。另一方面,你必须为一切付出代价,包括生产力。也是为了这个。因此,即使java.util.Random的官方文档也说:“ java.util.Random 的实例是线程安全的。但是,跨线程并发使用同一个 java.util.Random 实例可能会遇到争用,从而导致性能不佳。考虑相反,在多线程设计中使用 ThreadLocalRandom ”。也就是说,在多线程应用程序中,当从多个线程主动使用Random时,最好使用ThreadLocalRandom类。它的用法与常规Random略有不同:
public static void main(String []args){
	int rand = ThreadLocalRandom.current().nextInt(1,7);
	System.out.println("Value: " + rand);
}
正如您所看到的,我们没有为其指定种子。Oracle 的官方教程:Concurrent Random Numbers中描述了此示例。您可以在评论中阅读有关此类的更多信息:“ Guide to ThreadLocalRandom in Java ”。
实践中的概率论或者您了解随机 - 5

StreamAPI 和随机

随着 Java 8 的发布,我们有了许多新功能。包括流API。这些变化也影响了随机值的生成。例如, Random类具有新方法,允许您获取具有随机值(如、或 )的Stream。例如: intdoublelong
import java.util.Random;

public class HelloWorld{
    public static void main(String []args){
        new Random().ints(10, 1, 7).forEach(n -> System.out.println(n));
    }
}
还有一个新类SplittableRandom
import java.util.SplittableRandom;

public class HelloWorld{
    public static void main(String []args){
        new SplittableRandom().ints(10, 1, 7).forEach(n -> System.out.println(n));
    }
}
您可以在此处阅读有关SplittableRandom和其他类 之间的区别的更多信息:“在 Java 中创建随机数的不同方法”。

结论

我认为值得得出结论。您需要仔细阅读所使用的类的 JavaDoc。像“随机”这样乍一看很简单的东西背后,隐藏着可能会开一个残酷玩笑的细微差别。#维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION