ThreadLocalRandom類原理分析

bingfeng發表於2021-10-13

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的一個變數,屬於執行緒級別。

相關文章