우아한테크코스 프리코스 1주차 미션인 숫자 야구 게임에서 난수를 생성하기 위해 Randoms API를 사용하라는 요구사항을 발견했다.
내가 평소에 난수를 생성하기 위해 사용했던 java.util.Random
과 무엇이 다른지 궁금해서 동작 원리를 가볍게 살펴봤다.
아래는 Randoms 클래스의 pickNumberInRange()의 구현부다.
파라미터로 범위의 시작(startInclusive)과 끝(endInclusive)을 나타내는 정수를 받는 것을 확인할 수 있다.
메서드가 호출되면 가장 먼저 파라미터로 넘어온 startInclusive와 endInclusive를 다음과 같이 검증한다.
검증 메서드에서는 범위의 시작이 끝보다 클 때와 끝이 최대로 나타낼 수 있는 정수값과 같을 때, 그리고 범위가 너무 클 때 예외를 발생시킨다.
다시 돌아가서 파라미터 검증 후 값을 반환하는 부분을 보자.
return startInclusive + defaultRandom.nextInt(endInclusive - startInclusive + 1);
이는 시작점과 defaultRandom.nextInt()
를 더한 값을 반환한다는 것을 의미한다. 이를 통해 범위의 시작과 끝이 1, 9라고 가정할 때 ‘1 + 0~8’, 즉 "‘1~9’를 반환하겠구나"라고 대충 짐작할 수는 있을 것이다.
그래도 궁금하다.. defaultRandom.nextInt()
는 뭐하는 친구일까?
Randoms 클래스가 가진 defaultRandom은 ThreadLocalRandom.current()
에서 반환된 값을 담고 있다.
ThreadLocalRandom...? 타입이 Random이라는 것과 클래스 이름을 봐서는 Random을 상속한 친구인 듯 하다..
그냥 넘어갈 수는 없어 공식문서를 통해 ThreadLocalRandom이 무엇인지부터 알아봤다.
으음.. ThreadLocalRandom은 Random 클래스의 동시성 문제를 해결하기 위해서 사용하는 클래스라고 한다.
멀티 스레드 환경에서 Random API를 사용하면 경합 문제가 발생할 수 있는데, 이를 해결하기 위해 각 스레드마다 생성된 인스턴스에서 난수를 반환하여 안전하다는 이점이 존재한다. (아직 멀티 스레드, 동시성 문제에 대한 개념은 추후에 다루겠다..😇)
그리고 이 ThreadLocalRandom의 current()
는 현재 스레드의 ThreadLocalRandom을 반환하는 메서드다.
결국 defaultRandom은 현재 스레드의 ThreadLocalRandom임을 의미하고, defaultRandom.nextInt(n)
는 0과 n 사이의 의사 난수를 반환하는 친구임을 알게 되었다.
지금까지 알게 된 내용을 바탕으로 Randoms.pickNumberInRange()
의 동작 원리를 정리하자.
특정 범위의 최소값(startInclusive)과 최대값(endInclusive)을 파라미터로 받아온 뒤, 이 두 숫자를 검증한다. 예를 들어 최소값 위치에 최대값보다 큰 숫자가 있는 것은 아닌지 등등..
검증이 성공적으로 끝나면 startInclusive와 defaultRandom.nextInt()를 더한 결과를 반환한다.
이때 defaultRandom.nextInt()
에 endInclusive에서 startInclusive를 빼고 1을 더한 값을 넣는 이유는 java.util.concurrent.ThreadLocalRandom
의 nextInt()
가 0부터 n 미만의 난수를 생성하기 때문이다.
그래서 1, 9를 파라미터로 넘겨주게 되면, 1부터 9까지의 난수를 반환하는 것이다.
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html
https://velog.io/@sojukang/Random-대신-ThreadLocalRandom을-써야-하는-이유