1、Random類及其局限性
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
// 計算新的種子
int r = next(31);
int m = bound - 1;
// 根據新的種子計算亂數
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小結:
- 面試:多執行緒下Random存在什么樣的問題?
每個Random實體里面都有一個原子性的種子變數用來記錄當前的種子值,當要生成新的亂數時需要根據當前的種子計算新的種子并更新種子變數,當在多執行緒環境下,多個執行緒會競爭同一個原子變數的更新操作,由于原子變數的更新時CAS操作,同時只有一個執行緒會成功,所以會造成大量執行緒進行自旋重試,從而降低并發性能,
可能出現的癥狀:
如果并發請求非常多,自旋鎖一直重試,那么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);
}
這個方法用來創建ThreadLocalRandom亂數生成器,如果當前執行緒中threadLocalRandomProbe的變數值為0,則說明是第一次呼叫current方法,那么就呼叫localInit方法初始化種子變數,
這里使用了延遲初始化,在localInit方法中,并沒有初始化種子變數,而是在需要生成亂數的時候再生成種子變數,這是一種優化,
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
// 生成種子
int r = mix32(nextSeed());
int m = bound - 1;
if ((bound & m) == 0) // power of two
r &= m;
else { // reject over-represented candidates
for (int u = r >>> 1;
u + m - (r = u % bound) < 0;
u = mix32(nextSeed()) >>> 1)
;
}
return r;
}
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
// 生成新種子(獲取當前執行緒種子 + 種子增量)
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
mix32是一個固定的演算法,這里重點看下nextSeed方法,當第一次呼叫的時候進行初始化,獲取當前執行緒threadLocalRandomSeed的值(第一次默認值為0) + 種子增量,如果不是第一次那么獲取舊種子的值 + 種子增量生成新的種子變數并設定回去,這樣的話多執行緒環境下就避免了競爭,因為threadLocalRandomSeed是Thread的一個變數,屬于執行緒級別,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/333220.html
標籤:Java
