在建立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