ThreadLocalRandom
자바7에서 추가된 기능중에 ThreadLocalRandom 가 있다.
스레드 별로 난수 생성을 고립시키는 랜덤 클래스이다.
java.lang.Math#random 처럼 ThreadLocalRandom 역시 내부에 고정된 holder를 통해 난수를 생성한다.
문제는 멀티스레드에서 이 메서드를 사용할 경우 적절하게 동기화를 시켜야하는데,
많은 수의 스레드가 난수를 생성하려고 할 경우 각자의 랜덤 생성기를 가지게 하는 방법을 통해
서로간의 경합으로 인해 성능이 떨어지는 것을 방지하라고 주석에 적혀있다.
ThreadLocalRandom 의 경우 current() 라는 정적 메서드를 통해 객체를 얻도록 되어 있다.
public static ThreadLocalRandom current() {
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0) {
localInit();
}
return instance;
}
최초에는 localInit()이 호출되고 동일 스레드가 호출하면 바로 instance를 반환한다.
다른 스레드에 의해 current()가 호출되면 localInit()가 또 다시 호출이된다.
UNSAFE에서 getInt를 해오는데, 0이면 설정이 안되어 잇다고 보면 된다. Thread와 랜덤으로 바뀌는 PROBE 값 두개를 바탕으로 sun.misc.Unsafe 에 저장을 해놓는다.
- http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
- http://rangken.github.io/blog/2015/sun.misc.unSafe/
static final void localInit() {
int p = probeGenerator.addAndGet(PROBE_INCREMENT);
int probe = (p == 0) ? 1 : p; // skip 0
long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
Thread t = Thread.currentThread();
UNSAFE.putLong(t, SEED, seed);
UNSAFE.putInt(t, PROBE, probe);
}
probeGenerator와 seeder는 모두 AtomicLong 타입이고, seeder 의 초깃값은 seederjava.util.secureRandomSeed 이 true일 경우에는
SecureRandom을 통해 시드를 생성하고, 그 외에는 시간을 기반으로 생성한다.
이런 스레드의
threadLocalRandomSeed: 232
threadLocalRandomProbe: 240
threadLocalRandomSecondarySeed: 244
seedUniquifier: 8006678197202707420
Random
또한 Java 7에서는 Random 클래스도 seed 값 설정 부분이 바뀌었다.
기존에는 Java 6 에는 아래와 같이 정의했었다.
public Random()
{
this(System.currentTimeMillis());
}
public Random(long seed)
{
setSeed(seed);
}
이렇게 되면 현재 시간을 시드 값으로 설정하게 된다.
Java 7에서는 개선된 시드 설정으로 바뀌게 된다.
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
시간 단위도 밀리초에서 나노초로 세밀해졌다.
- JEP 356: Enhanced Pseudo-Random Number Generators
- https://bugs.openjdk.java.net/browse/JDK-8193209
Bugs
JDK-6955840 : ThreadLocalRandom bug - overridden setSeed(long) method is not invoked for java.util.Random(long)
- Submitted: 2010-05-26
- Resolved: 2011-05-18
JDK-7051516 : ThreadLocalRandom seed is never initialized so all instances generate the same sequence
- Submitted: 2011-06-04
- Resolved: 2012-05-13
JDK-6937857 : Concurrent calls to new Random() not random enough
- Resolved: 2011-03-08
- Submitted: 2010-03-24
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class RandomSeedCollisions {
public static void main(String[] args) throws Throwable {
class RandomCollector implements Runnable {
long[] randoms = new long[1<<16];
public void run() {
for (int i = 0; i < randoms.length; i++)
randoms[i] = new Random().nextLong();
}};
final int threadCount = 2;
List<RandomCollector> collectors = new ArrayList<RandomCollector>();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < threadCount; i++) {
RandomCollector r = new RandomCollector();
collectors.add(r);
threads.add(new Thread(r));
}
for (Thread thread : threads)
thread.start();
for (Thread thread : threads)
thread.join();
int collisions = 0;
HashSet<Long> s = new HashSet<Long>();
for (RandomCollector r : collectors) {
for (long x : r.randoms) {
if (s.contains(x))
collisions++;
s.add(x);
}
}
System.out.printf("collisions=%d%n", collisions);
if (collisions > 10)
throw new Error("too many collisions");
}
}
'Programing > JVM(Java, Kotlin)' 카테고리의 다른 글
[Java] forEach와 for each 의 차이점은? (5) | 2019.10.05 |
---|---|
[Java] 인스턴스 변수 초기화 (0) | 2019.09.18 |
[Java] JRE update 업데이트된 라이센스 조항 (0) | 2019.07.18 |
[macOS] JDK 삭제하기 (0) | 2019.07.17 |
[Java] LinkedList가 과거에는 doubly circular linked list? (0) | 2019.07.09 |