ThreadLocalRandom类原理

这是一个随机数生成器,而我们比较常用的是Random类,但是它是有一定的局限性。首先看一下Random生成随机数过程

Random类实现及局限

我们常用的一个方法是Random.nextInt(),那就以它作为例子:

public int nextInt (int bound) {
	if (bound <= 0)
		throw new IllealArgumentException(BadBound);
		
	//(1)根据老种子生成新种子
	int r = next(31);
	//根据新种子计算随机数
	...
	return r;
}

总结为两个步骤:

  • 首先根据老种子生成新种子
  • 根据新种子计算新的随机数

此处最关键的就是生成新种子的过程,因为根据种子生成随机数的算法是固定的。若多个线程都获得了同一个老种子,那么多个线程就会生成同一个新种子,也就会生成同样的随机数。因此要保证代码(1)的原子性,只有一个线程能够根据旧的种子获得新种子,那样其他线程就会丢掉自己获得的老种子而使用上一个线程的新种子来计算自己的新种子。Random函数使用了一个原子变量达到了这个效果。看一下next()的代码:

protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();			//获得旧的seed
            nextseed = (oldseed * multiplier + addend) & mask;		//计算新的seed
        } while (!seed.compareAndSet(oldseed, nextseed));			//若seed在此期间没有变仍是oldseed,则更新为新的seed
        return (int)(nextseed >>> (48 - bits));
    }

Random类中有一个seed变量,首先得到旧的种子,然后计算新的种子,利用compareAndSet()方法将当前种子和oldseed进行比较,如果两者不相等则说明此时seed已经被其他线程更改过,而且方法会返回false,则while()条件为true。继续循环,直到两者相等时方法返回true且while条件为false,中断循环且更新为nextseed。

Random类总结

每个Random实例都有一个原子性的种子seed变量类型为AtomicLong,利用它来生成新种子并更新这个变量。当多线程使用单个Random实例生成随机数时,会同时竞争这个变量的“使用权”,利用CAS操作,会导致大量线程进行自旋重试,降低了并发性能。

ThreadLocalRandom类

和ThreadLocal原理是类似的,因为Random的缺点是多个线程会使用同一个原子性种子变量,从而导致对原子变量更新的竞争。那么每个线程都维护一个种子变量,则每个线程生成随机数时都根据自己老的种子计算新的种子,并利用新种子更新老种子从而计算随机数,这样不会存在竞争问题。
多个线程竞争同一个变量
ThreadLocalRandom原理如图所示:
ThreadLocalRandom实现原理
首先看一下ThreadLocalRandom的类图:
在这里插入图片描述
能够看到在ThreadLocalRandom中并没有存放具体的种子,具体的种子存放在具体的调用线程的threadLocalRandomSeed变量中。和ThreadLocal类似,

  • 当线程调用ThreadLocalRandom中的current()方法时,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子
  • 当调用ThreadLocalRandom的nextInt()方法时,实际是获取当前线程的threadLocalRandomSeed变量作为当前种子来计算新种子,然后将新种子更新到当前线程的threadLocalRandomSeed变量,然后根据这个新种子计算随机数。

ThreadLocalRandom实现机制

1. 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 = 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);
        }
    }

2. ThreadLocalRandom current()方法

该方法用来获取ThreadLocalRandom实例,并初始化调用线程中的threadLocalRandomSeed和threadLocalRandomProbe变量。

	static final ThreadLocalRandom instance = new ThreadLocalRandom();
	public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)		//如果当前线程的threadLocalRandomProbe变量为0,进行初始化
            localInit();
        return instance;
    }

	static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0  ,不让PROBE变量为0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);		//初始化当前线程的threadLocalRandomSeed变量
        UNSAFE.putInt(t, PROBE, probe);		//初始化当前线程的threadLocalRandomProbe变量
    }

线程的threadLocalRandomProbe变量默认为0,所以如果检测到当前线程的这个变量为0,则说明此线程是第一次调用ThreadLocalRandom的current()方法,则调用localInit()方法初始化当前线程的种子变量。注意多个线程通过current获得的ThreadLocalRandom实例,其实获取的是同一个实例instance,但因为种子都在自己的线程里,所以ThreadLocalRandom只需存放一些通用算法即可。

3. int 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 { // reject over-represented candidates
            for (int u = r >>> 1;
                 u + m - (r = u % bound) < 0;
                 u = mix32(nextSeed()) >>> 1)
                ;
        }
        return r;
    }

	final long nextSeed() {
        Thread t; long r; // read and update per-thread seed
        
        //获取当前线程中的种子,并在其基础上累加GAMMA作为新种子,
       // 利用putLong更新到threadLocalRandomSeed变量中
        UNSAFE.putLong(t = Thread.currentThread(), SEED,		
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
    }

总结

ThreadLocalRandom使用ThreadLocal的原理,让每个线程都持有一个本地的种子变量,该种子变量只有在使用随机数时才会被初始化(判断threadLocalRandomProbe变量的值),多线程下计算新种子时则是根据自己线程内维护的种子变量进行更新,从而避免了竞争。

相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页