[java 随机数] ThreadLocalRandom、Random
真理具有相对性。
----马克思
文章目录
- [java 随机数] ThreadLocalRandom、Random
- 一、什么是随机数: 真随机数? 假随机数?
- 二、Random和ThreadLocalRandom的区别?
- 2.1 Random使用以及原理浅析
- 2.1.1 基础使用
- 2.1.2源码浅析
- 2.1.3 Random性能问题
- 2.2 ThreadLocalRandom使用和原理浅析
- 2.2.1 基本使用
- 2.2.2 源码浅析
计算机世界的随机数也是相对性的,计算机不可能还原真实的真正随机。
本篇文章主要解决两个问题:
- 什么是随机数: 真随机数? 假随机数?
- Random和ThreadLocalRandom的区别和使用场景?
一、什么是随机数: 真随机数? 假随机数?
若将真假随机数可视化,可以得到以下两个随机数
- 真随机数图像
真正的随机数是使用物理现象产生的:比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等等,这样的随机数发生器叫做物理性随机数发生器,它们的缺点是技术要求比较高。
- 假随机数图像
计算机中的随机函数是按照一定算法模拟产生的,其结果是确定的,是可见的。我们可以这样认为这个可预见的结果其出现的概率是100%。所以用计算机随机函数所产生的“随机数”并不随机,是伪随机数。
简而言之: 伪随机数是一组有规律的数,计算机生成的都是伪随机数。但这个规律周期可能会很长,所以在使用的时候不会感觉到周期性。
二、Random和ThreadLocalRandom的区别?
2.1 Random使用以及原理浅析
2.1.1 基础使用
//1. 不带sead的
new Random().nextInt();
/**
public Random() {
// 使用CAS和当前纳秒时间来获取sead
this(seedUniquifier() ^ System.nanoTime());
}
*/
// 2. 带sead的使用, sead用于生成
new Random(1000).nextInt();
/**
public Random(long seed) {
if (getClass() == Random.class)
// initialScramble 方法就是: (seed ^ multiplier) & mask
this.seed = new AtomicLong(initialScramble(seed));
else {
// 给Random子类用
this.seed = new AtomicLong();
setSeed(seed);
}
}
*/
2.1.2源码浅析
以上是java中Random对的一个简单使用, sead是伪随机数生成的一个关键状态字,示例中的nextInt()会调用next()方法:next(32);
protected int next(int bits) {
long oldseed, nextseed;
// 使用CAS操作来生成nextseed(返回值),不同的线程使用各自实例的sead
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
2.1.3 Random性能问题
使用Random没有现成安全的问题,但在高负载情况下,多个线程会在上文的CAS操作处争抢设置nextseed
,此时肯定有一些线程执行CAS连续失败,进而导致线程阻塞。
为解决这种线程争抢-堵塞问题,jdk在concurrent包中添加了ThreadLocalRandom类。
可以点击这个文档查看Random和ThreadLocalRandom的性能对比分析(基于JHM): 性能分析
2.2 ThreadLocalRandom使用和原理浅析
2.2.1 基本使用
@Test
public void testRandom() {
ThreadLocalRandom localRandom = ThreadLocalRandom.current();
/*数字*/
localRandom.nextInt(10); //[0,10)
localRandom.nextLong(); //返回伪随机long
localRandom.nextInt(2, 10); //[2,10)
localRandom.nextDouble(); //[0,1)
/*streams*/
DoubleStream doubles = localRandom.doubles();
doubles.forEach(System.out::println);
localRandom.ints();
}
2.2.2 源码浅析
ThreadLocalRandom 只有一个静态方法: current()
,用于获取当前线程的ThreadLocalRandom实例。正如下文代码所示,在获取实例的过程中,调用Unsafe方法来初始化一些关键字段: seed、probe。
static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThreadLocalRandom current() {
// 未初始化则初始化
if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
localInit();
return instance;
}
/**
初始化当前线程的线程字段。
仅在Thread.threadLocalRandomProbe为零时调用,表示需要生成线程本地种子值。
warn: 即使初始化纯粹是线程局部的,我们也需要依靠(静态)原子生成器来初始化值。
*/
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);
}
其中核心的Unsafe使用代码如下所示,ThreadLocalRandom类的核心逻辑都是通过Unsafe的实例UNSAFE
来实现的。此外由于在类加载后内存大小已经确定了,偏移量自然也确定了,因此不会存在修改不合法地址的情况。
// Unsafe mechanics
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;
/**
1. Unsafe.objectFieldOffset 可以获取到属性在对象内存的偏移量
2. Unsafe.getDeclaredField 返回一个Field对象,该对象反映此Class对象表示的类或接口的指定声明字段。 如果此Class对象表示数组类型,则建议使用java.lang.Class#getDeclaredFields方法
*/
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception e) {
throw new Error(e);
}
}
以下以nextInt()
方法举一个例子来说明ThreadLocalRandom的原理,其中核心操作就是nextSeed()
方法
public int nextInt() {
return mix32(nextSeed());
}
// 对seed进行位移操作
private static int mix32(long z) {
z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);
}
// 使用Unsafe修改SEED变量
final long nextSeed() {
Thread t; long r; // read and update per-thread seed
UNSAFE.putLong(t = Thread.currentThread(), SEED,
r = UNSAFE.getLong(t, SEED) + GAMMA);
return r;
}
参考链接