1. Обзор
В выпуске Java SE 17
представлено обновление API для генерации случайных чисел — JEP 356 .
С этим обновлением API были введены новые типы интерфейсов, а также методы для простого перечисления, поиска и создания экземпляров фабрик генераторов . Кроме того, теперь доступен новый набор реализаций генератора случайных чисел.
В этом руководстве мы сравним новый API RandomGenerator
со старым API Random
. Мы рассмотрим перечисление всех доступных фабрик генераторов и выбор генератора на основе его имени или свойства.
Мы также изучим потокобезопасность и производительность нового API.
2. Старый случайный API
Во-первых, давайте взглянем на старый API Java для генерации случайных чисел на основе класса Random .
2.1. Дизайн API
Оригинальный API состоит из четырех классов без интерфейсов:
2.2. Случайный
Чаще всего используется генератор случайных чисел Random
из пакета java.util
.
Чтобы сгенерировать поток случайных чисел, нам нужно создать экземпляр класса генератора случайных чисел — Random
:
Random random = new Random();
int number = random.nextInt(10);
assertThat(number).isPositive().isLessThan(10);
Здесь конструктор по умолчанию устанавливает начальное значение генератора случайных чисел в значение, которое, скорее всего, отличается от любого другого вызова.
2.3. Альтернативы
В дополнение к java.util.Random
доступны три альтернативных генератора для решения проблем безопасности потоков и безопасности .
Все экземпляры Random
по умолчанию потокобезопасны. Однако одновременное использование одного и того же экземпляра в нескольких потоках может привести к снижению производительности. Поэтому класс ThreadLocalRandom из пакета
java.util.concurrent
является предпочтительным вариантом для многопоточных систем.
Поскольку экземпляры Random
не являются криптографически безопасными, класс SecureRandom
позволяет нам создавать генераторы для использования в контексте, чувствительном к безопасности.
Наконец, класс SplittableRandom
из пакета java.util
оптимизирован для работы с параллельными потоками и вычислениями в стиле fork/join.
3. Новый API генератора случайных чисел
Теперь давайте взглянем на новый API, основанный на интерфейсе RandomGenerator
.
3.1. Дизайн API
Новый API обеспечивает лучший общий дизайн с новыми типами интерфейса и реализациями генератора :
На диаграмме выше мы видим, как старые классы API вписываются в новый дизайн. Помимо этих типов, добавлено несколько классов реализации генератора случайных чисел:
Группа Хороширо
Xoroshiro128PlusPlus
Группа Хосиро
Xoshiro256PlusPlus
Группа LXM
L128X1024СмешанныйСлучайный
L128X128MixСлучайный
L128X256СмешанныйСлучайный
L32X64MixСлучайный
L64X1024MixСлучайный
L64X128MixСлучайный
L64X128ЗвездаЗвездаСлучайный
L64X256MixСлучайный
3.2. Области улучшения
Отсутствие интерфейсов в старом API усложняло переключение между различными реализациями генератора. Поэтому третьим сторонам было сложно предоставить свои собственные реализации.
Например, SplittableRandom
был полностью отделен от остального API, хотя некоторые части его кода были полностью идентичны Random
.
Поэтому основными целями нового RandomGenerator
API являются:
- Обеспечьте более простую взаимозаменяемость различных алгоритмов
- Обеспечьте лучшую поддержку потокового программирования
- Устранение дублирования кода в существующих классах
- Сохранить существующее поведение старого
Random
API
3.3. Новые интерфейсы
Новый корневой интерфейс RandomGenerator
предоставляет единый API для всех существующих и новых генераторов .
Он определяет методы для возврата случайно выбранных значений разных типов, а также потоков случайно выбранных значений.
Новый API предоставляет дополнительные четыре новых специализированных интерфейса генератора:
SplitableGenerator
позволяет создать новый генератор как потомка текущего.JumpableGenerator
позволяет перескакивать вперед на умеренное количество розыгрышейLeapableGenerator
позволяет опережать большое количество розыгрышейПроизвольноJumpableGenerator
добавляет расстояние прыжка кLeapableGenerator
4. Фабрика случайных генераторов
В новом API доступен фабричный класс для генерации нескольких генераторов случайных чисел по определенному алгоритму.
4.1. Найти все
Метод RandomGeneratorFactory
создает непустой поток всех доступных фабрик генераторов .
Мы можем использовать его для печати всех зарегистрированных фабрик генераторов и проверки свойств их алгоритма :
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
через API поставщика услуг.
4.2. Найти по свойству
Мы также можем использовать метод all
для запроса фабрик по свойствам алгоритма генератора случайных чисел :
RandomGeneratorFactory.all()
.filter(RandomGeneratorFactory::isJumpable)
.findAny()
.map(RandomGeneratorFactory::create)
.orElseThrow(() -> new RuntimeException("Error creating a generator"));
Поэтому с помощью Stream API мы можем найти фабрику, удовлетворяющую нашим требованиям, а затем использовать ее для создания генератора.
5. Выбор случайного генератора
В дополнение к обновленному дизайну API было реализовано несколько новых алгоритмов, и, вероятно, в будущем будет добавлено больше.
5.1. Выберите по умолчанию
В большинстве случаев у нас нет конкретных требований к генератору. Таким образом, мы можем получить генератор по умолчанию непосредственно из интерфейса RandomGenerator
.
Это новый рекомендуемый подход в Java 17 в качестве альтернативы созданию экземпляров 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. Производительность
Давайте запустим простой тест производительности для всех доступных реализаций генератора в Java 17.
Мы будем тестировать генераторы по одному и тому же методу, генерируя четыре разных типа случайных чисел:
private static void generateRandomNumbers(RandomGenerator generator) {
generator.nextLong();
generator.nextInt();
generator.nextFloat();
generator.nextDouble();
}
Посмотрим на результаты тестов:
| Алгоритм | Режим | Счет | Ошибка | Единицы |
| L128X1024СмешанныйСлучайный | среднее | 95 637 | ±3274 | нс/оп |
| L128X128MixСлучайный | среднее | 57 899 | ±2162 | нс/оп |
| L128X256СмешанныйСлучайный | среднее | 66 095 | ±3260 | нс/оп |
| L32X64MixСлучайный | среднее | 35 717 | ±1737 | нс/оп |
| L64X1024MixСлучайный | среднее | 73 690 | ±4967 | нс/оп |
| L64X128MixСлучайный | среднее | 35 261 | ±1985 | нс/оп |
| L64X128ЗвездаЗвездаСлучайный | среднее | 34 054 | ±0,314 | нс/оп |
| L64X256MixСлучайный | среднее | 36 238 | ±0,090 | нс/оп |
| Случайный | среднее | 111 369 | ±0,329 | нс/оп |
| SecureRandom | среднее | 9 457 881 | ±45 574 | нс/оп |
| РазделяемыйСлучайный | среднее | 27 753 | ±0,526 | нс/оп |
| Xoroshiro128PlusPlus | среднее | 31 825 | ±1863 | нс/оп |
| Xoshiro256PlusPlus | среднее | 33 327 | ±0,555 | нс/оп |
SecureRandom
— самый медленный генератор, но это потому, что это единственный криптографически сильный генератор.
Поскольку им не обязательно быть потокобезопасными, новые реализации генераторов работают быстрее по сравнению с Random
.
8. Заключение
В этой статье мы рассмотрели обновления в API для генерации случайных чисел, новую функцию в Java SE 17
.
Мы узнали о различиях между старым и новым API. Включая представленный новый дизайн API, интерфейсы и реализации.
В примерах мы увидели, как найти подходящий алгоритм генератора с помощью RandomGeneratorFactory
. Мы также увидели, как выбрать алгоритм на основе его имени или свойства.
Наконец, мы рассмотрели потокобезопасность и производительность старой и новой реализации генератора.
Как всегда, полный исходный код доступен на GitHub .