ThreadLocalRandom

1、Random

在建立Random類時會生成seed,seed用於生成隨機數。在每次生成隨機數後,都會使用CAS的方式更新seed,因此Random是線程安全的。可是在併發度較高的狀況下,CAS的效率會變得很低。java

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

2、ThreadLocalRandom

  1. current()
static final ThreadLocalRandom instance = new ThreadLocalRandom();

public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}

current()首先查看當前線程中的PROBE(探針)是否初始化,若是是,則返回一個ThreadLocalRandom的單例;若是否,則調用localInit()進行初始化。算法

  1. localInit()
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);
}

localInit()生成probe和seed,並存入當前線程。probe和seed在Thread中的定義以下:安全

@sun.misc.Contended("tlr")
int threadLocalRandomProbe;

@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
  1. nextInt()
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;
}

因而可知,雖然ThreadLocalRandom是單例,全部線程共用一個,可是生成隨機數的nextInt()倒是使用各自線程中的seed,線程之間是相互隔離的。因此ThreadLocalRandom在高併發場景下的性能要優於Random。併發

  1. 使用

錯誤使用:dom

public static void main(String[] args) throws Exception{
	ThreadLocalRandom threadLocalRandom=ThreadLocalRandom.current();
	for(int i=1; i<=10; i++){
		new Thread(()->{
			System.out.println(Thread.currentThread().getName()+":"+threadLocalRandom.nextInt());
		},String.valueOf(i)).start();
	}
}

結果:高併發

因爲current()是在主線程中調用的,seed和probe初始化在了主線程中,而其餘線程在調用nextInt時取到的seed和probe都爲0,因爲隨機數生成算法都是固定的,因此也就生成了相同的隨機數。性能

正確使用:this

public static void main(String[] args) throws Exception{
	for(int i=1; i<=10; i++){
		new Thread(()->{
			System.out.println(Thread.currentThread().getName()+":"+ThreadLocalRandom.current().nextInt());
		},String.valueOf(i)).start();
	}
}

結果:線程

必定要在各自的線程中初始化。code

相關文章
相關標籤/搜索