讲ThreadLocalRandom之前,需要了解一下Random类的用法及其原理。
使用方法: 下例子为循环输出10次0~9之间的数值。
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(10)); } } }构造方法: 这就需要我们点开Random类了,我们从下方的构造器就可以看出,Random实例的生成需要一个Long类型的数值,没有就自己调用方法生成Long类型数值传入。
// 无参数构造器 public Random() { //调用有参数构造器,System.nanoTime()返回一个纳秒级别的数值,精度很小 this(seedUniquifier() ^ System.nanoTime()); } //将seedUniquifier替换一个新值并返回旧值 private static long seedUniquifier() { for (;;) { long current = seedUniquifier.get(); long next = current * 181783497276652981L; if (seedUniquifier.compareAndSet(current, next)) return next; } } private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L); //有参数构造器 public Random(long seed) { if (getClass() == Random.class) this.seed = new AtomicLong(initialScramble(seed)); else { this.seed = new AtomicLong(); setSeed(seed); } } private static long initialScramble(long seed) { return (seed ^ multiplier) & mask; } private static final long multiplier = 0x5DEECE66DL; private static final long mask = (1L << 48) - 1;方法调用: nextInt()方法,利用旧种子计算出新种子并替换,并利用新种子计算出随机数。
//提供一个范围参数 public int nextInt(int bound) { if (bound <= 0) throw new IllegalArgumentException(BadBound); int r = next(31); int m = bound - 1; if ((bound & m) == 0) r = (int)((bound * (long)r) >> 31); else { for (int u = r; u - (r = u % bound) + m < 0; u = 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)); } private static final long addend = 0xBL;Random类大致流程: ①:Random类实例的构建需要一个long类型数值,如果没有传入,则会自己构建一个传入。 ②:nextInt()方法的大致流程为使用Random实例的旧种子计算出新种子并替换旧种子,用新种子计算出随机数。 每个Random类都有一个原子类的seed变量来记录当前的种子数值,当多个线程都需要使用CAS操作更新种子数值,但只会有一个线程成功,会造成大量的线程进行自旋重试,降低并发性能,所以就有了ThreadLocalRandom。
在看到ThreadLocalRandom的第一时间,就感觉有点熟悉,这不是ThreadLocal类的赶脚么。其实他两的原理基本相同,都是在Thread中有储存值的变量。Random类的缺点就是多个线程争夺一个原子性种子变量,导致性能问题。那么我们只要像ThreadLocal一样在每个Thread中内置一个种子便可以解决问题。
用法:
public class RandomTest { public static void main(String[] args) { ThreadLocalRandom current = ThreadLocalRandom.current(); for (int i=0;i<10;i++){ System.out.println(current.nextInt(10)); } } }静态方法:
private static final sun.misc.Unsafe UNSAFE; //也就是说下面这三个数值记载的是Thread中变量的偏移量 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; //获取Thread类中threadLocalRandomSeed的偏移量 SEED = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSeed")); //获取Thread类中threadLocalRandomProbe的偏移量 PROBE = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomProbe")); //获取Thread类中threadLocalRandomSecondarySeed的偏移量 SECONDARY = UNSAFE.objectFieldOffset (tk.getDeclaredField("threadLocalRandomSecondarySeed")); } catch (Exception e) { throw new Error(e); } } }构造方法:
static final ThreadLocalRandom instance = new ThreadLocalRandom(); public static ThreadLocalRandom current() { //获取当前线程threadLocalRandomSeed变量的值,若为0,则是第一次,需要初始化。 //记住,PROBE记录的为调用线程的threadLocalRandomSeed的偏移量。 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); } private static final int PROBE_INCREMENT = 0x9e3779b9; private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL; private static final AtomicLong seeder = new AtomicLong(initialSeed()); private static final AtomicInteger probeGenerator = new AtomicInteger(); private static long mix64(long z) { z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL; z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L; return z ^ (z >>> 33); }方法调用:
public int nextInt(int bound) { if (bound <= 0) throw new IllegalArgumentException(BadBound); //返回新种子,并利用新种子进行随机数运算。 int r = mix32(nextSeed()); int m = bound - 1; if ((bound & m) == 0) r &= m; else { for (int u = r >>> 1; u + m - (r = u % bound) < 0; u = mix32(nextSeed()) >>> 1) ; } return r; } //新旧种子更新,在原有基础上累加GAMMA作为新种子,并将新种子返回 final long nextSeed() { Thread t; long r; UNSAFE.putLong(t = Thread.currentThread(), SEED, r = UNSAFE.getLong(t, SEED) + GAMMA); return r; } private static final long GAMMA = 0x9e3779b97f4a7c15L;