Random Number Generators in Java 17 – Java中的随机数生成器 17

最后修改: 2022年 1月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

The release of Java SE 17 introduces an update to the API for random number generation – JEP 356.

Java SE 17的发布引入了对随机数生成API的更新 – JEP 356

With this API update, new interface types have been introduced, as well as methods to easily list, find and instantiate generator factories. In addition, a new set of random number generator implementations is now available.

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

In this tutorial, we’ll compare the new RandomGenerator API with the old Random API. We’ll look at listing all available generator factories and selecting a generator based on its name or property.

在本教程中,我们将比较新的RandomGenerator API和旧的Random API。我们将研究列出所有可用的生成器工厂,并根据其名称或属性选择一个生成器。

We’ll also explore the new API’s thread-safety and performance.

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

2. Old Random API

2.旧的随机API

First, let’s take a look at Java’s old API for random number generation based on the Random class.

首先,让我们看看Java基于Random类的随机数生成的旧API。

2.1. API Design

2.1.API设计

The original API consists of four classes with no interfaces:

原始API由四个类组成,没有接口。

java random api

2.2. Random

2.2 随机

The most commonly used random number generator is Random from the java.util package.

最常用的随机数发生器是Random,来自java.util包。

To generate a stream of random numbers, we need to create an instance of a random number generator class – Random:

为了生成随机数流,我们需要创建一个随机数生成器类的实例 – Random

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

Here, the default constructor sets the seed of the random number generator to a value that is very likely distinct from any other invocation.

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

2.3. Alternatives

2.3.替代品

In addition to java.util.Random, three alternative generators are available to tackle thread safety and security concerns.

除了java.util.Random之外,还有三个替代生成器可用于解决线程安全和保障问题

All instances of Random are thread-safe by default. However, concurrent use of the same instance across threads may result in poor performance. Therefore, a ThreadLocalRandom class from the java.util.concurrent package is a preferred option for multithreaded systems.

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

As Random instances are not cryptographically secure, the SecureRandom class enables us to create generators for use in a security-sensitive context.

由于Random实例在密码学上并不安全,SecureRandom类使我们能够创建生成器用于安全敏感的环境。

Finally, the SplittableRandom class from java.util package is optimized for working with parallel streams and fork/join-style computations.

最后,来自java.util包的SplittableRandom类为处理并行流和fork/join式计算进行了优化。

3. New RandomGenerator API

3.新的RandomGenerator API

Now, let’s take a look at the new API based on the RandomGenerator interface.

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

3.1. API Design

3.1.API设计

The new API provides a better overall design with new interface types and generator implementations:

新的API通过新的接口类型和生成器实现提供了一个更好的整体设计。

rng old api

In the diagram above, we can see how the old API classes fit into the new design. On top of these types, there are several random number generator implementation classes added:

在上图中,我们可以看到旧的API类是如何融入新的设计中的。在这些类型的基础上,还增加了几个随机数发生器的实现类。

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

3.2. Improvement Areas

3.2.改进领域

The lack of interfaces in the old API made it harder to switch between different generator implementations. Therefore, it was difficult for third parties to provide their own implementations.

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

For example, SplittableRandom was completely detached from the rest of the API even though some pieces of its code were completely identical to Random.

例如,SplittableRandom完全脱离了API的其他部分,尽管它的一些代码与Random完全相同。

Therefore, the main goals of the new RandomGenerator API are:

因此,新的RandomGenerator API的主要目标是。

  • Ensure easier interchangeably of different algorithms
  • Enable better support for stream-based programming
  • Eliminate code duplication in existing classes
  • Preserve existing behavior of the old Random API

3.3. New Interfaces

3.3.新的接口

The new root interface RandomGenerator provides a uniform API for all existing and new generators.

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

It defines methods for returning randomly chosen values of different types, as well as streams of randomly chosen values.

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

The new API provides additional four new specialized generator interfaces:

新的API提供了额外的四个新的专门的发生器接口。

  • SplitableGenerator enables creating a new generator as a descendent of the current one
  • JumpableGenerator allows jumping ahead a moderate number of draws
  • LeapableGenerator allows jumping ahead a large number of draws
  • ArbitrarilyJumpableGenerator adds jump distance to LeapableGenerator

4. RandomGeneratorFactory

4.RandomGeneratorFactory

A factory class for generating multiple random number generators of a specific algorithm is available in the new API.

在新的API中,有一个工厂类用于生成特定算法的多个随机数生成器。

4.1. Find All

4.1 查找所有

The RandomGeneratorFactory method all produces a non-empty stream of all available generator factories.

RandomGeneratorFactory方法all产生一个非空的所有可用生成器工厂的流。

We may use it to print all registered generator factories and check the properties of their algorithm:

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

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())));

Availability of factories is determined by locating implementations of the RandomGenerator interface via the service provider API.

工厂的可用性是通过服务提供者的API找到RandomGenerator接口的实现来确定的。

4.2. Find by Property

4.2.按属性查找

We can also make use of the all method to query factories by properties of the random number generator algorithm:

我们还可以利用all方法来按随机数生成器算法的属性查询工厂

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

Therefore, using the Stream API, we can find a factory that fulfills our requirements and then use it to create a generator.

因此,使用Stream API,我们可以找到一个符合我们要求的工厂,然后用它来创建一个发生器。

5. RandomGenerator Selection

5.RandomGenerator选择

In addition to the updated API design, several new algorithms have been implemented, and more are likely to be added in the future.

除了更新的API设计外,还实现了几种新的算法,而且未来可能会有更多的算法加入。

5.1. Select Default

5.1.选择默认值

In most cases, we don’t have specific generator requirements. Thus, we can fetch the default generator directly from the RandomGenerator interface.

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

This is the new recommended approach in Java 17, as an alternative to creating instances of Random:

这是Java 17中新推荐的方法,作为创建Random实例的一种替代。

RandomGenerator generator = RandomGenerator.getDefault();

The getDefault method currently selects the L32X64MixRandom generator.

getDefault方法目前选择L32X64MixRandom发生器。

However, algorithms can change over time. Therefore, there’s no guarantee that this method will continue to return this algorithm in future releases.

然而,算法可以随着时间的推移而改变。因此,不能保证这个方法在未来的版本中会继续返回这种算法。

5.2. Select Specific

5.2.选择特定

On the other hand, when we do have specific generator requirements, we can retrieve a specific generator using the of method:

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

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

This method requires the name of the random number generator to be passed as a parameter.

这个方法需要将随机数生成器的名称作为一个参数传递。

It will throw an IllegalArgumentException if the named algorithm is not found.

如果没有找到命名的算法,它将抛出一个IllegalArgumentException

6. Thread Safety

6.螺纹安全

Most of the new generator implementations are not thread-safe. However, both Random and SecureRandom still are.

大多数新的生成器实现都不是线程安全的。然而,RandomSecureRandom仍然是。

Thus, in multithreaded environments, we can choose to either:

因此,在多线程环境中,我们可以选择以下两种方式。

  • Share an instance of a thread-safe generator
  • Split a new instance from a local source before a new thread is started

We can achieve the second case using a SplittableGenerator:

我们可以使用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));
    });
})

This way, we ensure that our generator instances get initialized in a way that doesn’t result in identical streams of numbers.

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

7. Performance

7.业绩

Let’s run a simple performance test for all the available generator implementations in Java 17.

让我们对Java 17中所有可用的生成器实现进行一个简单的性能测试。

We’ll test the generators on the same method generating four different types of random numbers:

我们将在生成四种不同类型的随机数的同一方法上测试生成器。

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

Let’s look at the benchmark results:

让我们来看看基准测试结果。

Algorithm Mode Score Error Units
L128X1024MixRandom avgt 95,637  ±3,274 ns/op
L128X128MixRandom avgt 57,899  ±2,162 ns/op
L128X256MixRandom avgt 66,095  ±3,260 ns/op
L32X64MixRandom avgt 35,717  ±1,737 ns/op
L64X1024MixRandom avgt 73,690  ±4,967 ns/op
L64X128MixRandom avgt 35,261  ±1,985 ns/op
L64X128StarStarRandom avgt 34,054  ±0,314 ns/op
L64X256MixRandom avgt 36,238  ±0,090 ns/op
Random avgt 111,369  ±0,329 ns/op
SecureRandom avgt 9,457,881  ±45,574 ns/op
SplittableRandom avgt 27,753  ±0,526 ns/op
Xoroshiro128PlusPlus avgt 31,825  ±1,863 ns/op
Xoshiro256PlusPlus avgt 33,327  ±0,555 ns/op

 

SecureRandom is the slowest generator, but this is because it’s the only cryptographically strong generator.

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

As they don’t have to be thread-safe, the new generator implementations perform faster compared to Random.

由于它们不必是线程安全的,新的生成器实现与Random相比表现得更快

8. Conclusion

8.结语

In this article, we explored updates in the API for random number generation, a new feature in Java SE 17.

在这篇文章中,我们探讨了随机数生成的API的更新,这是Java SE 17中的一项新功能。

We learned the differences between the old and the new API. Including the new API design, interfaces, and implementations that were introduced.

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

In the examples, we saw how to find a suitable generator algorithm using the RandomGeneratorFactory. We also saw how to select an algorithm based on its name or property.

在这些例子中,我们看到了如何使用RandomGeneratorFactory找到一个合适的生成器算法。我们还看到了如何根据算法的名称或属性来选择算法。

Finally, we looked at thread safety and performance of the old and new generator implementations.

最后,我们研究了新旧发电机实现的线程安全和性能。

As always, the complete source code is available over on GitHub.

一如既往,完整的源代码可在GitHub上获得