Java并發編程 - ThreadLocalRandom

Random類的API文檔描述中有這樣的一句話:

Instances of java.util.Random are threadsafe. However, the concurrent use of the same java.util.Random instance across threads may encounter contention and consequent poor performance. Consider instead using ThreadLocalRandom in multithreaded designs.

java.util.Random類的實例是線程安全的。然而,跨線程并發的情況下使用java.util.Random實例會產生競爭,從而導致性能不佳。在多線程設計中考慮使用ThreadLocalRandom。

我們知道,Random類使用線性同余的方式來生成偽隨機數。在創建Random類的實例時會確定用于生成隨機數的稱為"種子"的數,這個"種子"在調用隨機數生成方法后會重新被計算并且進行重設,內部代碼使用的CAS操作來保證并發過程中的原子性,如下:

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

上面說得多線程并發的情況出現爭用,說得就是對這個"種子"的重設操作。使用無鎖的CAS算法保證了Random是線程安全的,但是由于還是要處理爭用,所以還是會影響性能。

ThreadLocalRandom就是用來解決多線程并發的情況下生成隨機數的性能問題。"ThreadLocal"這個詞應該很熟悉了吧,從這個類的取名也就看出,它的實例是線程隔離的。

這個類的用法通常應該是這樣的形式: ThreadLocalRandom.current().nextX(...) (其中X是Int , Long ,等)。 當所有用法都是這種形式時,絕對不可能跨多個線程共享一個ThreadLocalRandom 。

current()

current()方法代碼如下:

/**
 * Returns the current thread's {@code ThreadLocalRandom}.
 *
 * @return the current thread's {@code ThreadLocalRandom}
 */
public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}

這個方法的作用如其注釋所示:返回當前線程的ThreadLocalRandom。

UNSAFE.getInt(Thread.currentThread(), PROBE) == 0

這段代碼的作用是判斷當前線程的threadLocalRandomProbe屬性是否為0。

PROBE定義如下:

Class<?> tk = Thread.class;
PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));

Thread類定義了一個名為threadLocalRandomProbe的屬性:

/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;

若當前線程的threadLocalRandomProbe這個探針屬性還為設置,則執行localInit()方法。

這里要注意,執行完localInit方法后會返回ThreadLocalRandom實例,也就是方法中看到的instance的,instance的定義如下:

/** The common ThreadLocalRandom */
static final ThreadLocalRandom instance = new ThreadLocalRandom();

可以看到這個相當于一個單例。也就是說多線程的情況下都統一調用current()方法,那么返回的就是同一個ThreadLocalRandom實例。

localInit()

localInit()方法源碼如下:

/**
 * Initialize Thread fields for the current thread.  Called only
 * when Thread.threadLocalRandomProbe is zero, indicating that a
 * thread local seed value needs to be generated. Note that even
 * though the initialization is purely thread-local, we need to
 * rely on (static) atomic generators to initialize the values.
 */
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);
}

上面的方法會生成"種子"和"探針"值,然后設置到當前線程中。Thread類中有存儲"種子"的屬性,如下:

java.lang.Thread

/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;

nextInt()

nextInt()源代碼如下:

/**
 * Returns a pseudorandom {@code int} value.
 *
 * @return a pseudorandom {@code int} value
 */
public int nextInt() {
    return mix32(nextSeed());
}

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;
}

可以看到操作是線程獨立的,當在多線程的環境下調用nextInt()方法時,每次都是通過當前線程內部的"種子"來生成隨機數,線程與線程之間是沒有干擾的。

總結

ThreadLocalRandom適用于多線程,是因為"種子"不再像Random類那樣多線程共享,而是每個線程內部都保留著一個屬于自己的"種子",線程操作的是這個線程局部的"種子",避免了生成隨機數時"種子"的爭搶,從而提升性能。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。