在建立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)); }
static final ThreadLocalRandom instance = new ThreadLocalRandom(); public static ThreadLocalRandom current() { if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0) localInit(); return instance; }
current()首先查看當前線程中的PROBE(探針)是否初始化,若是是,則返回一個ThreadLocalRandom的單例;若是否,則調用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;
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。併發
錯誤使用: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