ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

第三章 ThreadLocalRandom原理剖析

2021-09-22 20:02:44  阅读:167  来源: 互联网

标签:第三章 ThreadLocalRandom 剖析 线程 种子 随机数 多线程 变量


第三章 ThreadLocalRandom原理剖析

ThreadLocalRandom类是jdk7在JUC包下新增的随机数生成器,弥补了Random类在多线程下的缺陷。

1. Random类及其局限性

1.1 Random类源码简析

Random类内部简析
生成新的随机数需要两个步骤

  1. 首先根据老的种子生成新的种子 nextseed=f(oldseed) (1)
  2. 然后根据新种子计算新的随机数 r=g(nextseed,bounds) (2)

1.2 Random类解决多线程生成随机数

在单线程下每次生成随机数之前都是根据老种子计算出新种子,保证了随机数产生的随机性。多线程环境下,多个线程可能同时拿到同一个老种子去计算新种子【步骤(1)】,这会导致多个线程产生相同的随机数值【步骤(2)算法固定】。所以步骤(1)要保持原子性,即当多线程根据同一个老种子计算新种子时,第一个线程的新种子计算出来并更新后,第二个线程要丢弃自己的老的种子,而使用第一个线程的新种子来重新计算自己的新种子,依次类推,这样才能保证多线程下随机数是随机的。

Random类通过使用一个原子变量达到这个效果 private final AtomicLong seed

1.3 next函数剖析


protected int next(int bits){
	long oldseed,nextseed;
	AtomicLong seed=this.seed;
	do{
		//获取当前种子原子变量的值
		oldseed=seed.get();
		//根据当前种子生成新种子 抽象为 nextseed=f(oldseed)
		nextseed=(oldseed * multiplier + addend) & mask; 
		//使用CAS操作,用新种子更新旧种子 CAS操作会保证只有一个线程可以更新老的种子为新的,其他线程都会失败,通过循环重新获取更新后的种子作为当前种子去计算新种子
	}while(!seed.compareAndSet(oldseed,nextseed));
	//根据新种子生成新的随机数
	return (int)(nextseed>>>(48-bits));
}

Random类的next函数剖析

总结:每个Random实例里面都有一个原子性的种子变量用来记录当前的种子值,当要生成新的随机数值时,需要根据当前种子计算新的种子并更新回原子变量。在多线程环境下使用单个Random实例生成随机数时,当多个线程同时计算随机数来计算新的种子时,多个线程会竞争同一个原子变量的更新操作,由于原子变量的更新是CAS操作,同时只能有一个线程成功,会造成大量线程进行自选重试,降低并发性能。

1.4 多线程下的局限性

多线程环境下,多个线程同时生成随机数时,由于**种子原子变量的CAS操作,同时只能有一个现成成功,会造成大量线程自旋重试**,降低了并发性能。

2. ThreadLocalRandom

获取随机数生成器 ThreadLocalRandom random=ThreadLocalRandom.current()
从名字可以联想到ThreadLocal:通过让每个线程复制一份变量,使得每个线程对变量进行操作时实际是操作自己本地内存里面的副本,避免对共享变量进行同步。Random缺点是多线程会使用同一个原子性种子变量,导致对原子变量的竞争。
如果每个线程维护一个种子变量,则每个线程生成随机数时根据自己的老种子计算新种子,并使用新种子更新老种子,再计算随机数,就不存在竞争问题,提高并发性能。ThreadLocalRandom就是这个原理
ThreadLocalRandom原理图

3. 源码分析

ThreadLocalRandom类

  • ThreadLocalRandom类继承了Random类并重写了nextInt方法,在ThreadLocalRandom类中并没有存放具体的种子,具体的种子放在具体的调用线程的threadLocalRandomSeed变量里面。ThreadLocalRandom类似于ThreadLocal类,是一个工具类。当线程调用ThreadLocalRandom的current方法时,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子。

  • 当调用ThreadLocalRandom的nextInt方法时,实际是获取调用线程的threadLocalRandomSeed变量作为当前种子来计算新种子,然后更新新种子到当前线程的threadLocalRandomSeed变量,而后在根据新种子并使用具体算法算出新的随机数值。

  • threadLocalRandomSeed变量是Thread类里面一个普通long数据类型的变量,并不是原子性变量,因为这个变量是线程级别的,根本不需要使用原子性变量。

  • 其中seeder和probeGenerator是两个原子性变量,在初始化调用线程的种子和探针变量时用到,每个线程只会用到一次。

  • 变量instance是ThreadLocalRandom的一个实例,该变量是static的。当多个线程通过current方法获取实例时,其实获取的是同一个实例。但是由于具体的种子是放在线程里面的,所以在ThreadLocalRandom的实例里面只包含与线程无关的通用算法,所以它是线程安全的。

3.1 Unsafe机制

//Unsafe machine
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 Exception(e);
	}
}

3.2 ThreadLocalRandom current()方法


private ThreadLocalRandom(){
	initialized=true;
}

static final ThreadLocalRandom instance=new ThreadLocalRandom();

public static ThreadLocalRandom current(){
	// 1
	if(UNSAFE.getInt(Thread.currentThread(),PROBE)==0){
		// 2
		localInit();
	}
	// 3
	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);
}
  1. 如果当前线程中threadLocalRandomProbe的变量值为0(默认情况下线程的这个变量值为0),则说明当前线程是第一次调用ThreadLocalRandom的current方法,那么需要调用localInit方法计算当前线程的初始化种子变量。这里为了延迟初始化,在不需要使用随机数功能时就不初始化Thread类的种子变量,这是一种优化。
  2. 首先根据probeGenerator计算当前线程中threadLocalRandomProbe的初始化值,然后根据seeder计算当前线程的初始化种子,而后把这两个变量设置到当前线程
  3. 返回ThreadLocalRandom的实例,这个方法是静态的,多个线程返回的是同一个ThreadLocalRandom实例

3.3 nextInt方法

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

}

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

4. 总结

  1. Random实现原理及Random在多线程下需要竞争种子原子变量更新操作的缺点

  2. ThreadLocalRandom类引用ThreadLocal原理,让每个线程持有一个本地的种子变量,该种子变量只有在使用随机数时才会被初始化(延迟初始化、懒加载)

  3. 多线程下计算新种子时根据自己线程内维护的种子变量进行更新,避免了竞争

标签:第三章,ThreadLocalRandom,剖析,线程,种子,随机数,多线程,变量
来源: https://blog.csdn.net/qq_33828816/article/details/120413532

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有