先说结论
试测数据:
- 基于常见场景:在一个数据范围区间内生成随机数。
- 1000万的随机数范围,for循环生成50万个随机数。无其他额外操作。
SDK包 | 性能耗时 | 评价 |
---|---|---|
java.util.Random | 9毫秒 | 1、编写简单,方法较多,也快速。很多SDK包都基于此扩展。 2、随机性重度依赖seed的情况,seed一样,分配的随机数和顺序一样。 3、线程不安全。 |
ThreadLocalRandom | 8毫秒 | 1、继承于java.util.Random 2、与线程绑定,一个线程一个,多线程下安全。 3、seed情况部分借助于线程的内存地址等随机信息,来提升随机性。 |
Math.Random | 14毫秒 | 1、用法上比较费劲,只能生成double。 2、内部有借用java.util.Random |
SecureRandom | 142毫秒 | 线程安全,seed不可预测(借助于系统中的随机事件信息) |
Apache#RandomDataGenerator | 54毫秒 | API比较丰富,特殊场景下考虑。 |
it.unimi.dsi#XoRoShiRo128PlusRandom | 17毫秒 | 偏门的三方包。比较快。线程不安全。 |
1. Math.random() 静态方法
产生的随机数是 0 - 1 之间的一个 double
,即 0 <= random <= 1
。
使用:
for (int i = 0; i < 10; i++) {
System.out.println(Math.random());
}
结果:
0.3598613895606426
0.2666778145365811
0.25090731064243355
0.011064998061666276
0.600686228175639
0.9084006027629496
0.12700524654847833
0.6084605849069343
0.7290804782514261
0.9923831908303121
实现原理:
When this method is first called, it creates a single new pseudorandom-number generator, exactly as if by the expression new java.util.Random() This new pseudorandom-number generator is used thereafter for all calls to this method and is used nowhere else.
当第一次调用 Math.random() 方法时,自动创建了一个伪随机数生成器,实际上用的是 new java.util.Random()
。当接下来继续调用 Math.random() 方法时,就会使用这个新的伪随机数生成器。
源码如下:
public static double random() {
Random rnd = randomNumberGenerator;
if (rnd == null) rnd = initRNG(); // 第一次调用,创建一个伪随机数生成器
return rnd.nextDouble();
}
private static synchronized Random initRNG() {
Random rnd = randomNumberGenerator;
return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd; // 实际上用的是new java.util.Random()
}
initRNG() 方法是 synchronized 的,因此在多线程情况下,只有一个线程会负责创建伪随机数生成器(使用当前时间作为种子),其他线程则利用该伪随机数生成器产生随机数。Java生成随机数的几种高级用法,这篇推荐看一下。
因此 Math.random() 方法是线程安全的
。
什么情况下随机数的生成线程不安全?
- 线程1在第一次调用 random() 时产生一个生成器 generator1,使用当前时间作为种子。
- 线程2在第一次调用 random() 时产生一个生成器 generator2,使用当前时间作为种子。
- 碰巧 generator1 和 generator2 使用相同的种子,导致 generator1 以后产生的随机数每次都和 generator2 以后产生的随机数相同。
什么情况下随机数的生成线程安全?: Math.random() 静态方法使用
- 线程1在第一次调用 random() 时产生一个生成器 generator1,使用当前时间作为种子。
- 线程2在第一次调用 random() 时发现已经有一个生成器 generator1,则直接使用生成器 generator1。
public class JavaRandom {
public static void main(String args