ThreadLocalRandom類原理分析
本文轉(zhuǎn)載自微信公眾號「一個程序員的成長」,作者一個程序員的成長。轉(zhuǎn)載本文請聯(lián)系一個程序員的成長公眾號。
1、Random類及其局限性
- public int nextInt(int bound) {
- if (bound <= 0)
- throw new IllegalArgumentException(BadBound);
- // 計算新的種子
- int r = next(31);
- int m = bound - 1;
- // 根據(jù)新的種子計算隨機數(shù)
- if ((bound & m) == 0) // i.e., bound is a power of 2
- r = (int)((bound * (long)r) >> 31);
- else {
- for (int u = r;
- u - (r = u % bound) + m < 0;
- u = next(31))
- ;
- }
- return r;
- }
- protected int next(int bits) {
- long oldseed, nextseed;
- // 這是一個原子性的變量
- AtomicLong seed = this.seed;
- do {
- // (1)、獲取老的種子
- oldseed = seed.get();
- // (2)、計算出新的種子
- nextseed = (oldseed * multiplier + addend) & mask;
- // (3)、CAS操作更新老的種子
- } while (!seed.compareAndSet(oldseed, nextseed));
- return (int)(nextseed >>> (48 - bits));
- }
Random小結(jié):
- 面試:多線程下Random存在什么樣的問題?
每個Random實例里面都有一個原子性的種子變量用來記錄當(dāng)前的種子值,當(dāng)要生成新的隨機數(shù)時需要根據(jù)當(dāng)前的種子計算新的種子并更新種子變量。當(dāng)在多線程環(huán)境下,多個線程會競爭同一個原子變量的更新操作,由于原子變量的更新時CAS操作,同時只有一個線程會成功,所以會造成大量線程進行自旋重試,從而降低并發(fā)性能。
可能出現(xiàn)的癥狀:如果并發(fā)請求非常多,自旋鎖一直重試,那么CPU會一直飆升。
2、ThreadLocalRandom
- public static ThreadLocalRandom current() {
- if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
- localInit();
- return instance;
- }
- 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);
- }
這個方法用來創(chuàng)建ThreadLocalRandom隨機數(shù)生成器,如果當(dāng)前線程中threadLocalRandomProbe的變量值為0,則說明是第一次調(diào)用current方法,那么就調(diào)用localInit方法初始化種子變量。
這里使用了延遲初始化,在localInit方法中,并沒有初始化種子變量,而是在需要生成隨機數(shù)的時候再生成種子變量,這是一種優(yōu)化。
- 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);
- }
- final long nextSeed() {
- Thread t; long r; // read and update per-thread seed
- // 生成新種子(獲取當(dāng)前線程種子 + 種子增量)
- UNSAFE.putLong(t = Thread.currentThread(), SEED,
- r = UNSAFE.getLong(t, SEED) + GAMMA);
- return r;
- }
mix32是一個固定的算法,這里重點看下nextSeed方法,當(dāng)?shù)谝淮握{(diào)用的時候進行初始化,獲取當(dāng)前線程threadLocalRandomSeed的值(第一次默認值為0) + 種子增量,如果不是第一次那么獲取舊種子的值 + 種子增量生成新的種子變量并設(shè)置回去。這樣的話多線程環(huán)境下就避免了競爭,因為threadLocalRandomSeed是Thread的一個變量,屬于線程級別。