ThreadLocalRondom原理剖析

ThreadLocalRondom是JDK 7在并发包中新增的随机数生成器,该类弥补了Random类在并发环境下的缺陷。

Random的局限性

Random生成随机数的方法如下:

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

next方法通过计算生成新的种子。用原子变量来存放种子,多线程的情况下,CAS操作会保证只有一个线程可以更新老的种子为新种子,更新失败的线程进行自旋,这降低了并发性能,所以产生了ThreadLocalRandom。

protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            //seed计算公式,通过CAS操作进行更新
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
    	//将得到的值进行逻辑右移
        return (int)(nextseed >>> (48 - bits));
    }

ThreadLocalRandom简介

ThreadLocalRandom和ThreadLocal的原理相似,ThreadLocalRandom使得每个线程都维护自己独有的种子变量,这样就不存在竞争问题,大大提高并发性能。

current方法

在current方法中,获得ThreadLocalRandom实例并初始化。seed不再是一个AtomicLong变量,在Thread类中有三个变量。

  • threadLocalRandomSeed:使用它来控制随机数种子。
  • threadLocalRandomProbe:使用它来控制初始化。
  • threadLocalRandomSecondarySeed:二级种子。

这三个变量都加了sun.misc.Contended注解,用来避免伪共享问题。

   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; // skip 0
       long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
       Thread t = Thread.currentThread();
       UNSAFE.putLong(t, SEED, seed);
       UNSAFE.putInt(t, PROBE, probe);
   }


/** The current seed for a ThreadLocalRandom */
   @sun.misc.Contended("tlr")
   long threadLocalRandomSeed;

   /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
   @sun.misc.Contended("tlr")
   int threadLocalRandomProbe;

   /** Secondary seed isolated from public ThreadLocalRandom sequence */
   @sun.misc.Contended("tlr")
   int threadLocalRandomSecondarySeed;

Unsafe机制

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实例
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> tk = Thread.class;
        //获取Thread类里面threadLocalRandomSeed变量在Thread实例的偏移量
        SEED = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSeed"));
        //获取Thread类里面threadLocalRandomProbe变量在Thread实例的偏移量
        PROBE = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomProbe"));
        //获取Thread类里面threadLocalRandomSecondarySeed变量在Thread实例的偏移量
        SECONDARY = UNSAFE.objectFieldOffset
            (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

nextInt方法

nextInt方法用于获取下一个随机数。

public int nextInt(int bound) {
       //参数校验
       if (bound <= 0)
           throw new IllegalArgumentException(BadBound);
       //根据当前线程中的种子计算新种子
       int r = mix32(nextSeed());
       //根据新种子计算随机数
       int m = bound - 1;
       if ((bound & m) == 0) // power of two
           r &= m;
       else {
           for (int u = r >>> 1;
                u + m - (r = u % bound) < 0;
                u = mix32(nextSeed()) >>> 1)
               ;
       }
       return r;
   }

nextSeed方法

获取当前线程的threadLocalRandomSeed变量值,然后加上GAMMA值作为新种子。可参照上文Unsafe机制。

final long nextSeed() {
    Thread t; long r;
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                   r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}

参考

《Java并发编程之美》


ThreadLocalRondom原理剖析
https://l1n.wang/2020/Java并发/threadlocal-rondom/
作者
Lin Wang
发布于
2020年4月28日
许可协议