在许多应用程序中,您需要随机数。您可能需要在视频游戏中掷骰子,创建私有加密密钥或创建用户的临时密码。
所有这些应用程序都依赖于随机数的创建。有时很难区分何时使用什么,而安全性是一个深刻的话题。如果不花几年时间深入研究它,就很难快速理解有关可用实现的文档,并为您的用例选择正确的方法。
因此,在本教程中,我将总结突出的用例以及如何根据您的 Java 代码选择性能最佳的实现。
在本文中,您将了解:
如何生成整数、浮点数和布尔值,
如何为性能关键型用例生成随机数,
如何为安全关键用例生成随机数,
数字生成器的工作原理,
伪随机数生成器和真随机数生成器之间的差异,
如何利用种子对你有利。
所有代码示例都是最小的,您可以在GitHub上找到完整的源代码。
数学约束随机()
Math.random甚至在Java 6之前就已经存在了。它易于访问,并且仍然被广泛使用。在 Java 17 中,可以使用一个名为的新通用接口,该接口整合了当前 Java SDK 中的所有随机生成器实现。RandomGenerator
Math.random()如今,只需将 权限委派给 .但是,它只返回一个 .因此,它不允许您请求不同类型的数字或在范围之间生成数字。它也不允许你从不同的实现中进行选择。Random().nextFloat()double
在以下各节中,您将了解更灵活的数字生成,并了解如何生成针对效率或安全性进行优化的数字。
自 Java 17 以来的通用接口
在 Java 17 中,通用接口由 Java SDK 中的可用数字生成器实现。您可以使用适用于所有基本数据类型的方法,并且可以定义要为其生成数字的预期范围:
单线程环境中的性能优化随机数生成
对于许多与安全无关的情况,您并不关心随机数的可预测性。通常,您只想拥有可靠的分布。
与应用程序是单线程时可用的实现相比,性能更高的实现。一种非常有效的替代方案称为:RandomSplittableRandom
new SplittableRandom().nextInt();
在 MacBook Pro 上执行的比较“可拆分随机”和“随机”的基准测试显示以下结果:
SplittableRandom执行速度比在单线程环境中快 5 倍。Random
其他优点是确定性行为和可拆分的分叉/连接实现。总而言之,您应该更喜欢在单线程环境中使用。Random()SplittableRandomRandom
多线程环境中的性能优化随机数生成
高吞吐量应用程序利用多个线程。因此,您希望使用用于并行使用的数字生成器。
的实现是线程安全的,但相对较慢,并且由于锁定而减慢得更多。因为不是线程安全的,所以这里不是替代方案。RandomSplittableRandom
但是,通过在多线程环境中使用,可以获得更好的性能。它使用 ,但确保在多个线程中高性能且安全的使用:ThreadLocalRandomSplittableRandom
ThreadLocalRandom.current().nextInt();
在 MacBook Pro 上执行的基准测试使用 10 个线程比较线程本地随机数和随机生成数,显示以下结果:
如您所见,使用速度提高了425倍。 是无锁的,因此比线程安全类的性能更高。ThreadLocalRandomThreadLocalRandomRandom
安全优化的随机数生成
我们刚才讨论的方法对于您的大多数应用程序来说都是快速且足够的。但是,他们正在创建所谓的伪随机生成的数字。
他们不是总是创建一个真正的随机数,而是根据先前预测的数字预测一个新数字,这伴随着一个状态和严重的可预测性问题。
也许您想为加密创建长期存在的机密,并且您不希望其他人能够预测下一个生成的令牌。
在Java中,对于更多与安全性相关的用例::SecureRandom
SecureRandom.getInstanceStrong().nextInt();
SecureRandom.getInstanceStrong()为您提供一个提供程序,用于创建安全令牌。在许多 Linux 系统中,您使用 ,根据真实设备的随机噪声生成数字。/dev/random
但是,如果您没有收集足够的随机数据,即所谓的缺失熵,则执行可能会阻塞并花费意外的长时间。特别是在具有大量 Docker 容器的机器中,这可能会导致在实践中执行缓慢。
作为替代方法,在没有熵可用的情况下,默认情况下不阻塞。它还使用不太安全的数字生成方式作为回退。new SecureRandom()
如何利用种子发挥您的优势
默认情况下,伪数生成器使用随机种子,该种子反映用于生成值的起始值。因此,种子对于测试非常方便,因为它使您可以控制预测并允许您重置数字的创建方式。
到目前为止,我们还没有谈论过与种子有关的任何事情。
这使得测试变得容易得多。否则,您需要始终模拟依赖项。
为什么数字生成很难
了解为什么数字生成很难获得安全感至关重要。
工程师编写代码,最终编译成在实际处理单元(CPU)中执行的机器可读代码。CPU建立在电子电路上,电子电路由逻辑门组成。
长话短说,没有真正的随机性,你可以用传统计算机创建,因为输出需要一些输入,根据定义,这不可能是随机的。
这意味着您需要来自现实世界的某种真正的随机输入,例如来自电阻器的热噪声。有一些昂贵的硬件数字生成器使用现实世界的物理原理来为您提供大量的随机数创建容量。
不安全随机数生成的风险
尽管许多协议在设计上是安全的,但如果攻击者可以预测加密密钥,则它们不是。
如今,许多应用程序都需要在幕后生成真正的随机数。否则,攻击者可能能够预测生成的数字,并通过这样做渗透到应用程序中。
例如,如果攻击者突然可以立即解决加密问题,那么基于量子计算的安全相关处理突破可能是一个真正的威胁。
在这篇博客文章中,您学习了如何在 Java 中有效地生成数字。您还学习了如何优化性能或安全性,并了解了种子是什么以及如何使用它。
此外,您现在应该了解伪生成数和真随机生成数之间的主要区别,并且应该能够描述为什么安全随机数生成很重要。