1. 개요
Java SE 17 릴리스에는 난수 생성을 위한 API 업데이트인 JEP 356 이 도입되었습니다 .
이 API 업데이트를 통해 새로운 인터페이스 유형과 생성기 팩토리를 쉽게 나열하고 찾고 인스턴스화하는 방법이 도입되었습니다 . 또한 이제 새로운 난수 생성기 구현 세트를 사용할 수 있습니다.
이 사용방법(예제)에서는 새 RandomGenerator API를 이전 Random API와 비교합니다. 사용 가능한 모든 생성기 팩토리를 나열하고 이름 또는 속성을 기반으로 생성기를 선택하는 방법을 살펴보겠습니다.
또한 새 API의 스레드 안전성과 성능에 대해서도 살펴보겠습니다.
2. 예전 랜덤 API
먼저 Random 클래스 를 기반으로 난수 생성을 위한 Java의 이전 API를 살펴보겠습니다 .
2.1. API 설계
원래 API는 인터페이스가 없는 4개의 클래스로 구성됩니다.
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 문제를 해결할 수 있습니다 .
Random 의 모든 인스턴스는 기본적으로 스레드로부터 안전합니다. 그러나 여러 스레드에서 동일한 인스턴스를 동시에 사용하면 성능이 저하될 수 있습니다. 따라서 java.util.concurren t 패키지 의 ThreadLocalRandom 클래스는 다중 스레드 시스템에 선호되는 옵션입니다.
Random 인스턴스는 암호학적으로 안전하지 않기 때문에 SecureRandom 클래스 를 사용하면 Security에 민감한 컨텍스트에서 사용할 생성기를 만들 수 있습니다.
마지막으로 java.util 패키지의 SplittableRandom 클래스는 병렬 스트림 및 fork/join 스타일 계산 작업에 최적화되어 있습니다.
3. 새로운 RandomGenerator API
이제 RandomGenerator 인터페이스 를 기반으로 하는 새로운 API를 살펴보겠습니다 .
3.1. API 설계
새로운 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. 스레드 안전성
대부분의 새 생성기 구현은 스레드로부터 안전하지 않습니다 . 그러나 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 | ±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에서 사용할 수 있습니다 .