1. 개요

Java SE 17 릴리스에는 난수 생성을 위한 API 업데이트인 JEP 356 이 도입되었습니다 .

이 API 업데이트를 통해 새로운 인터페이스 유형과 생성기 팩토리를 쉽게 나열하고 찾고 인스턴스화하는 방법이 도입되었습니다 . 또한 이제 새로운 난수 생성기 구현 세트를 사용할 수 있습니다.

이 사용방법(예제)에서는 새 RandomGenerator API를 이전 Random API와 비교합니다. 사용 가능한 모든 생성기 팩토리를 나열하고 이름 또는 속성을 기반으로 생성기를 선택하는 방법을 살펴보겠습니다.

또한 새 API의 스레드 안전성과 성능에 대해서도 살펴보겠습니다.

2. 예전 랜덤 API

먼저 Random 클래스 를 기반으로 난수 생성을 위한 Java의 이전 API를 살펴보겠습니다 .

AdChoices
광고

2.1. API 설계

원래 API는 인터페이스가 없는 4개의 클래스로 구성됩니다.

자바 랜덤 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 외에도 가지 대체 생성기를 사용하여 스레드 안전 및 Security 문제를 해결할 수 있습니다 .

AdChoices
광고

Random 의 모든 인스턴스는 기본적으로 스레드로부터 안전합니다. 그러나 여러 스레드에서 동일한 인스턴스를 동시에 사용하면 성능이 저하될 수 있습니다. 따라서 java.util.concurren t 패키지 의 ThreadLocalRandom 클래스는 다중 스레드 시스템에 선호되는 옵션입니다.

Random 인스턴스는 암호학적으로 안전하지 않기 때문에 SecureRandom  클래스 를 사용하면 Security에 민감한 컨텍스트에서 사용할 생성기를 만들 수 있습니다.

마지막으로 java.util 패키지의 SplittableRandom 클래스는 병렬 스트림 및 fork/join 스타일 계산 작업에 최적화되어 있습니다.

3. 새로운 RandomGenerator API

이제 RandomGenerator 인터페이스 를 기반으로 하는 새로운 API를 살펴보겠습니다 .

3.1. API 설계

새로운 API는 새로운 인터페이스 유형 및 생성기 구현 으로 더 나은 전체 디자인을 제공합니다 .

rng 오래된 API

위의 다이어그램에서 이전 API 클래스가 새 설계에 어떻게 부합하는지 확인할 수 있습니다. 이러한 유형 외에도 몇 가지 난수 생성기 구현 클래스가 추가되었습니다.

  • 소로시로 그룹
    • Xoroshiro128PlusPlus
  • 조시로 그룹
    • Xoshiro256PlusPlus
  • LXM 그룹
    • L128X1024믹스랜덤
    • L128X128믹스랜덤
    • L128X256믹스랜덤
    • L32X64Mix랜덤
    • L64X1024믹스랜덤
    • L64X128믹스랜덤
    • L64X128Star스타랜덤
    • L64X256믹스랜덤

3.2. 개선 영역

이전 API에는 인터페이스가 없었기 때문에 다른 생성기 구현 간에 전환하기가 더 어려웠습니다. 따라서 타사에서 자체 구현을 제공하기 어려웠습니다.

예를 들어, SplittableRandom 은 코드의 일부가 Random 과 완전히 동일했지만 API의 나머지 부분에서 완전히 분리되었습니다 .

따라서 새로운 RandomGenerator API의 주요 목표는 다음과 같습니다.

  • 서로 다른 알고리즘을 더 쉽게 상호 교환할 수 있도록 보장
  • 스트림 기반 프로그래밍에 대한 더 나은 지원 사용
  • 기존 클래스에서 코드 중복 제거
  • 이전 Random API 의 기존 동작 유지

3.3. 새로운 인터페이스

새 루트 인터페이스 인 RandomGenerator 는 모든 기존 및 새 생성기에 대해 균일한 API를 제공합니다 .

임의로 선택된 값의 스트림뿐만 아니라 다양한 유형의 임의로 선택된 값을 반환하는 방법을 정의합니다.

새로운 API는 4개의 새로운 특수 생성기 인터페이스를 추가로 제공합니다.

  • SplitableGenerator 를 사용하면 현재 생성기의 후손으로 새 생성기를 생성할 수 있습니다.
  • JumpableGenerator 를 사용하면 적당한 수의 드로우를 앞으로 점프할 수 있습니다.
  • LeapableGenerator 를 사용하면 많은 수의 무승부를 앞당길 수 있습니다.
  • ArbitrarilyJumpableGenerator 는 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())));

팩토리의 가용성은 서비스 제공자 API를 통해 RandomGenerator 인터페이스 구현을 찾아 결정됩니다.

4.2. 속성으로 찾기

난수 생성기 알고리즘의 속성으로 Factory을 쿼리 하기 위해 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 인터페이스 에서 직접 기본 생성기를 가져올 수 있습니다.

이것은 Random 의 인스턴스 생성에 대한 대안으로 Java 17의 새로운 권장 접근 방식입니다 .

RandomGenerator generator = RandomGenerator.getDefault();

getDefault 메서드 는 현재 L32X64MixRandom 생성기를 선택합니다.

그러나 알고리즘은 시간이 지남에 따라 변경될 수 있습니다. 따라서 이 메서드가 향후 릴리스에서 이 알고리즘을 계속 반환한다는 보장은 없습니다.

5.2. 특정 선택

반면에 특정 생성기 요구 사항이 있는 경우 of 메서드 를 사용하여 특정 생성기를 검색 할 수 있습니다 .

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

이 방법을 사용하려면 난수 생성기의 이름을 매개변수로 전달해야 합니다.

명명된 알고리즘을 찾을 수 없으면 IllegalArgumentException 이 발생합니다.

6. 스레드 안전성

대부분의 생성기 구현은 스레드로부터 안전하지 않습니다 . 그러나 RandomSecureRandom 은 여전히 ​​존재합니다.

따라서 다중 스레드 환경에서는 다음 중 하나를 선택할 수 있습니다.

  • 스레드로부터 안전한 생성기의 인스턴스 공유
  • 새 스레드가 시작되기 전에 로컬 소스에서 새 인스턴스 분할

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  ±3,274 NS/운영
L128X128믹스랜덤 평균 57,899  ±2,162 NS/운영
L128X256믹스랜덤 평균 66,095  ±3,260 NS/운영
L32X64Mix랜덤 평균 35,717  ±1,737 NS/운영
L64X1024믹스랜덤 평균 73,690  ±4,967 NS/운영
L64X128믹스랜덤 평균 35,261  ±1,985 NS/운영
L64X128Star스타랜덤 평균 34,054  ±0,314 NS/운영
L64X256믹스랜덤 평균 36,238  ±0,090 NS/운영
무작위의 평균 111,369  ±0,329 NS/운영
시큐어랜덤 평균 9,457,881  ±45,574 NS/운영
분할 가능랜덤 평균 27,753  ±0,526 NS/운영
Xoroshiro128PlusPlus 평균 31,825  ±1,863 NS/운영
Xoshiro256PlusPlus 평균 33,327  ±0,555 NS/운영

 

SecureRandom 은 가장 느린 생성기이지만 암호학적으로 강력한 유일한 생성기이기 때문입니다.

스레드로부터 안전할 필요가 없기 때문에 새로운 생성기 구현 은 Random 에 비해 더 빠르게 수행 됩니다.

8. 결론

이 기사에서는 Java SE 17 의 새로운 기능인 난수 생성을 위한 API의 업데이트를 살펴보았습니다 .

이전 API와 새 API의 차이점을 배웠습니다. 도입된 새로운 API 설계, 인터페이스 및 구현을 포함합니다.

예제에서 RandomGeneratorFactory 를 사용하여 적절한 생성기 알고리즘을 찾는 방법을 살펴보았습니다 . 또한 이름이나 속성을 기반으로 알고리즘을 선택하는 방법도 살펴보았습니다.

마지막으로 이전 및 새 생성기 구현의 스레드 안전성과 성능을 살펴보았습니다.

항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .

Generic footer banner