Random 类及其局限性 java.util.Random 是使用较为广泛的随机数生成工具类,使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class RandomTest { public static void main (String[] args) { Random random = new Random() ; for (int i = 0 ; i < 10 ; i++) { System.out.println(random.nextInt(5 )); } } }
Random 部分源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Random implements java .io .Serializable { private final AtomicLong seed; public int nextInt (int bound) { if (bound <= 0 ) throw new IllegalArgumentException(BadBound); int r = next(31 ); ··· return r; } 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)); } ··· }
通过阅读源码不难发现,每个Random 实例里都有一个原子性的种子变量用来记录当前的种子值,当要生成新的随机数时需要根据当前种子计算新的种子并更新回原子变量。在多线程下使用单个Random 实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS 操作,同时只有一个线程会成功,所以会造成大量线程进行自旋重试,这会降低并发性能,所以ThreadLocalRandom 应运而生。
ThreadlocalRandom 为了弥补多线程高并发情况下Random 的缺陷, 在JUC 包下新增了ThreadLocalRandom类,使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class RandomTest { public static void main (String[] args) { ThreadLocalRandom random2 = ThreadLocalRandom.current(); for (int i = 0 ; i < 10 ; i++) { System.out.println(random.nextInt(5 )); } } }
源码分析 Unsafe 机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private static final sun.misc.Unsafe UNSAFE;private static final long SEED;private static final long PROBE;private static final long SECONDARY;static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> tk = Thread.class; SEED = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSeed" )); PROBE = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomProbe" )); SECONDARY = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSecondarySeed" )); } catch (Exception e) { throw new Error(e); } }
ThreadLocalRandom current() 方法 此方法获取ThreadLocalRandom 实例,并初始化调用线程中的threadLocalRandomSeed 和threadLocalRandomProbe 变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static final ThreadLocalRandom instance = new 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; long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT)); Thread t = Thread.currentThread(); UNSAFE.putLong(t, SEED, seed); UNSAFE.putInt(t, PROBE, probe); }
int nextInt(int bound) 方法 计算当前线程下一个随机数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int nextInt (int bound) { if (bound <= 0 ) throw new IllegalArgumentException(BadBound); int r = mix32(nextSeed()); ··· return r; } final long nextSeed () { Thread t; long r; UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA); return r; }
总结 ThreadLocalRandom 使用ThreadLocal 的原理,让每个线程都持有一个本地的种子变量,该种子变量只有在使用随机数时才会被初始化。在多线程下计算新种子时是根据自己线程内维护的种子变量进行更新,从而避免了竞争。