原创

Java 17中的随机数生成器

温馨提示:
本文最后更新于 2022年11月17日,已超过 19 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

1.概述

发布 Java SE 17 引入了随机数生成API的更新- JEP356.

通过此API更新, 引入了新的接口类型,以及轻松列出、查找和实例化生成器工厂的方法此外,一组新的随机数生成器实现现在可用。

在本教程中,我们将比较 随机生成器 旧API 随机的 应用程序编程接口。我们将列出所有可用的生成器工厂,并根据其名称或属性选择生成器。

我们还将探讨新API的线程安全性和性能。

2.旧的随机API

首先,让我们看看Java的旧API,它用于基于 随机的

2.1.API设计

原始API由四个没有接口的类组成:

2.2.随机

最常用的随机数生成器是 java.util包中的Random

要生成随机数流,我们需要 创建随机数生成器类的实例- random:

Random random = new Random();
int number = random.nextInt(10);
assertThat(number).isPositive().isLessThan(10);

这里,默认构造函数将随机数生成器的种子设置为一个很可能与任何其他调用不同的值。

2.3.备选方案

除了 java.util.Random, 有三种替代生成器可用于解决线程安全和安全问题.

所有Random实例 默认情况下是线程安全的。然而,跨线程并发使用同一实例可能会导致性能不佳。因此,java.util.concurrent包中的 ThreadLocalRandom是多线程系统的首选选项。

Random 实例不是加密安全的,而 SecureRandom 类使我们能够创建用于安全敏感上下文的生成器。

最后,java.util包中的SplittableRandom,该软件包针对并行流和fork/join风格的计算进行了优化。

3.新的RandomGenerator API

现在,让我们看看基于 RandomGenerator 接口的新的API.

3.1 API设计

新API提供了更好的总体设计 新的接口类型和生成器实现:

rng old api

在上图中,我们可以看到旧的API类如何适合新的设计。在这些类型之上,还添加了几个随机数生成器实现类:

  • Xoroshiro group
    • Xoroshiro128PlusPlus
  • Xoshiro group
    • Xoshiro256PlusPlus
  • LXM group
    • L128X1024MixRandom
    • L128X128MixRandom
    • L128X256MixRandom
    • L32X64MixRandom
    • L64X1024MixRandom
    • L64X128MixRandom
    • L64X128StarStarRandom
    • L64X256MixRandom

3.2.改进领域

这个旧 API 中缺少接口使得在不同的生成器实现之间切换变得更加困难。因此,第三方很难提供自己的实现。

例如 SplittableRandom 与API的其他部分完全分离,尽管它的一些代码与 Random有关.

因此 RandomGenerator API包括:

  • 确保不同算法更容易互换
  • 更好地支持基于流的编程
  • 消除现有类中的代码重复
  • 保留旧的Random  API现有行为 

3.3.新接口

新的根接口 RandomGenerator 为所有现有和新的生成器提供统一的API.

它定义了返回不同类型的随机选择值以及随机选择值流的方法。

新API提供了另外四个新的专用生成器接口:

  • SplitableGenerator 启用创建新生成器作为当前生成器的后代
  • JumpableGenerator 允许跳出适度的平局数
  • LeapableGenerator 允许大量的平局
  • ArbitrarilyJumpableGenerator将跳跃距离添加到 可跳发电机

4. RandomGeneratorFactory

新API中提供了用于生成特定算法的多个随机数生成器的工厂类。

4.1.查找全部

这个 RandomGeneratorFactory方法all是产生所有非空流工作工厂的。

我们可以用它来打印所有注册的生成器工厂并检查其算法的属性:

RandomGeneratorFactory.all()
  .sorted(Comparator.comparing(RandomGeneratorFactory::name))
  .forEach(factory -> System.out.println(String.format("%s\t%s\t%s\t%s",
    factory.group(),
    factory.name(),
    factory.isJumpable(),
    factory.isSplittable())));

工厂的可用性取决于 RandomGenerator 接口。

4.2.按属性查找

我们还可以利用 all 方法 根据随机数生成器算法的特性查询工厂:

RandomGeneratorFactory.all()
  .filter(RandomGeneratorFactory::isJumpable)
  .findAny()
  .map(RandomGeneratorFactory::create)
  .orElseThrow(() -> new RuntimeException("Error creating a generator"));

因此,使用Stream API,我们可以找到满足我们要求的工厂,然后使用它创建生成器。

5. RandomGenerator 选择

除了更新的API设计之外,还实现了一些新算法,未来可能会添加更多算法。

5.1.选择默认值

在大多数情况下,我们没有特定的生成要求。因此,我们可以 直接从 RandomGenerator 接口 创建默认的生成器。

这是Java17中推荐的新方法创建 Random 实例:

RandomGenerator generator = RandomGenerator.getDefault();

这个 getDefault 方法当前使用的是L32X64MixRandom生成器

然而,算法可能会随着时间而改变。因此,无法保证该方法在未来的版本中会继续返回该算法。

5.2.选择特定

另一方面,当我们确实有特定的生成要求时,我们可以 使用 of 方法:

RandomGenerator generator = RandomGenerator.of("L128X256MixRandom");

此方法要求将随机数生成器的名称作为参数传递。

如果找不到命名算法, 它会抛出一个 IllegalArgumentException

6.线程安全

大多数 这个 新的生成器实现不是线程安全的, 然而 Random SecureRandom也是一样。

因此,在多线程环境中,我们可以选择:

  • 共享线程安全生成器的实例
  • 在启动新线程之前从本地源拆分新实例

我们可以使用 SplittableGenerator:

List<Integer> numbers = Collections.synchronizedList(new ArrayList<>());
ExecutorService executorService = Executors.newCachedThreadPool();

RandomGenerator.SplittableGenerator sourceGenerator = RandomGeneratorFactory
    .<RandomGenerator.SplittableGenerator>of("L128X256MixRandom")
    .create();

sourceGenerator.splits(20).forEach((splitGenerator) -> {
    executorService.submit(() -> {
        numbers.add(splitGenerator.nextInt(10));
    });
})

这样,我们可以确保生成器实例的初始化方式不会导致相同的数字流。

7.性能

让我们为Java17中所有可用的生成器实现运行一个简单的性能测试。

我们将使用相同的方法测试生成器,生成四种不同类型的随机数:

private static void generateRandomNumbers(RandomGenerator generator) {
    generator.nextLong();
    generator.nextInt();
    generator.nextFloat();
    generator.nextDouble();
}

让我们看看基准测试结果:

AlgorithmModeScoreErrorUnits
L128X1024MixRandomavgt95,637 ±3,274ns/op
L128X128MixRandomavgt57,899 ±2,162ns/op
L128X256MixRandomavgt66,095 ±3,260ns/op
L32X64MixRandomavgt35,717 ±1,737ns/op
L64X1024MixRandomavgt73,690 ±4,967ns/op
L64X128MixRandomavgt35,261 ±1,985ns/op
L64X128StarStarRandomavgt34,054 ±0,314ns/op
L64X256MixRandomavgt36,238 ±0,090ns/op
Randomavgt111,369 ±0,329ns/op
SecureRandomavgt9,457,881 ±45,574ns/op
SplittableRandomavgt27,753 ±0,526ns/op
Xoroshiro128PlusPlusavgt31,825 ±1,863ns/op
Xoshiro256PlusPlusavgt33,327 ±0,555ns/op

SecureRandom 是最慢的生成器,但这是因为它是唯一一个密码强度强的生成器。

由于它们不必是线程安全的, 新的生成器实现执行速度Random更快 .

8.结论

在本文中,我们探讨了随机数生成API中的更新,这是 Java SE 17.

我们了解了旧API和新API之间的区别。包括引入的新API设计、接口和实现。

在示例中,我们看到了如何使用 RandomGeneratorFactory。我们还看到了如何根据其名称或属性选择算法。

最后,我们研究了新老生成器实现的线程安全性和性能。

一如既往,完整的源代码可用 在GitHub上.

正文到此结束