Random和ThreadLocalRandom分析简介
一、Random类
1、简介
Random 类用于生成伪随机数的流。 该类使用48位种子,其使用线性同余公式进行修改
Math.random()
使用起来相对更简单,但不是线程安全的;java.util.Random
的Random类是线程安全的。 但是跨线程的同时使用java.util.Random
实例可能会遇到争用,从而导致性能下降。 在多线程设计中考虑使用ThreadLocalRandom
类;java.util.Random
的Random不是加密安全的。 考虑使用SecureRandom
获取一个加密安全的伪随机数生成器,供安全敏感应用程序使用。
2、Random的构造函数
- Random():创建一个新的随机数生成器
1 | /** |
1 | private static long seedUniquifier() { |
- random(long seed):使用单个 Long 种子创建一个新的随机数生成器\
伪随机使用了线性同余法(具体可自行查阅资料)
1 | public Random(long seed) { |
3、next()核心方法
Random在多线程的环境是并发安全的,它解决竞争的方式是使用用原子类,本质上上也就是CAS + Volatile保证线程安全
在Random类中,有一个AtomicLong的域,用来保存随机种子。其中每次生成随机数时都会根据随机种子做移位操作以得到随机数。
1 | //Long类型的随机 |
以下是next方法的核心,使用seed种子,不断生成新的种子,然后使用CAS将其更新,再返回种子的移位后值。这里不断的循环CAS操作种子,直到成功。可见,Random实现原理主要是利用随机种子采用一定算法进行处理生成随机数,在随机种子的安全保证利用原子类AtomicLong。
1 | protected int next(int bits) { |
4、Random在并发下的缺点
虽然Random是线程安全,但是对于并发处理使用原子类AtomicLong在大量竞争时,使用同一个 Random 对象可能会导致线程阻塞,由于很多CAS操作会造成失败,不断的Spin,而造成CPU开销比较大而且吞吐量也会下降。
这里可以自行多线程测试,可以发现随着线程增加,Random随着竞争越来越激烈,然后耗时越来越多。然而ThreadLocalRandom随着线程数的增加,基本没有变化。所以在大并发的情况下,随机的选择,可以考虑ThreadLocalRandom提升性能。
二、ThreadLocalRandom
1、简介
ThreadLocalRandom
是Random
的子类,它是将Seed随机种子隔离到当前线程的随机数生成器,从而解决了Random
在Seed上竞争的问题,它的处理思想和ThreadLocal
本质相同。
Unsafe 类内的方法透露着一股 “Unsafe” 的气息,具体表现就是可以直接操作内存,而不做任何安全校验,如果有问题,则会在运行时抛出 Fatal Error
,导致整个虚拟机的退出。
2、原理分析
2.1 ThreadLocalRandom单例模式
从下述代码可以发现ThreadLocalRandom
使用了单例模式,即在一个Java应用中只有一个ThreadLocalRandom对象。当UNSAFE.getInt(Thread.currentThread(), PROBE)
返回0时,就执行localInit()
,最后返回单例。
1 | // 一个java应用只有一个实例 |
2.2 Seed随机种子隔离到当前线程
核心方法
1 | //会从 object 对象var1的内存地址偏移var2后的位置读取四个字节作为long型返回 |
UNSAFE.getInt(Thread.currentThread(), PROBE)
是获取当前Thread线程对象中的PROBE。
首先获取变量名SEED
、PROBE
等参数相对对象的偏移位置
1 | // Unsafe mechanics |
当第一次调用ThreadLocalRandom.current()
方法时当前线程检测到PROBE
未初始化会调用localInit()
方法进行初始化,并把当前线程的seed值和probe值存储在当前线程Thread对象内存地址偏移相对应变量的位置
1 | static final void localInit() { |
threadLocalRandomProbe
用于表示当前线程Thread是否初始化,如果是非0,表示其已经初始化。换句话说,该变量就是状态变量,用于标识当前线程Thread是否被初始化。threadLocalRandomSeed
从注释中也可以看出,它是当前线程的随机种子。随机种子分散在各个Thread对象中,从而避免了并发时的竞争点。
3、nextSeed()核心方法
nextSeed()
生成随机种子用来生成随机数序列
1 | public long nextLong() { |
因为在初始化的时候已经存储了当前线程的seed值和probe值到相应线程对象内存地址的偏移位置,调用nextSeed()
时直接从当前线程对象偏移位置处进行获取,并生成下一个随机数种子到该位置,同时使用了UNSAFE
类方法,不同线程间不需要竞争获得seed值,因此可以可以将竞争点隔离
1 | final long nextSeed() { |
三、总结
Random
是Java中提供的随机数生成器工具类,但是在大并发的情况下由于其随机种子的竞争会导致吞吐量下降,从而引入ThreadLocalRandom
它将竞争点隔离到每个线程中,从而消除了大并发情况下竞争问题,提升了性能。
并发竞争的整体优化思路:lock -> cas + volatile -> free lock
参考文章
https://mp.weixin.qq.com/s/f-lfqEUNvY6XRmCL-h0Qfg
https://www.cnblogs.com/lxyit/p/12654374.html
https://blog.csdn.net/tyh18226568070/article/details/105884912