触屏音乐网站源码,廊坊网站建设官网,重庆高端网站建设价格,软件技术开发合同作者 | 王磊来源 | Java中文社群#xff08;ID#xff1a;javacn666#xff09;转载请联系授权#xff08;微信ID#xff1a;GG_Stone#xff09;在 Java 中#xff0c;生成随机数的场景有很多#xff0c;所以本文我们就来盘点一下 4 种生成随机数的方式#xff0c;以… 作者 | 王磊来源 | Java中文社群IDjavacn666转载请联系授权微信IDGG_Stone在 Java 中生成随机数的场景有很多所以本文我们就来盘点一下 4 种生成随机数的方式以及它们之间的区别和每种生成方式所对应的场景。1.RandomRandom 类诞生于 JDK 1.0它产生的随机数是伪随机数也就是有规则的随机数。Random 使用的随机算法为 linear congruential pseudorandom number generator (LGC) 线性同余法伪随机数。在随机数生成时随机算法的起源数字称为种子数seed在种子数的基础上进行一定的变换从而产生需要的随机数字。Random 对象在种子数相同的情况下相同次数生成的随机数是相同的。比如两个种子数相同的 Random 对象第一次生成的随机数字完全相同第二次生成的随机数字也完全相同。默认情况下 new Random() 使用的是当前纳秒时间作为种子数的。① 基础使用使用 Random 生成一个从 0 到 10 的随机数不包含 10实现代码如下// 生成 Random 对象
Random random new Random();
for (int i 0; i 10; i) {// 生成 0-9 随机整数int number random.nextInt(10);System.out.println(生成随机数 number);
}
以上程序的执行结果为② 优缺点分析Random 使用 LGC 算法生成伪随机数的优点是执行效率比较高生成的速度比较快。它的缺点是如果 Random 的随机种子一样的话每次生成的随机数都是可预测的都是一样的。如下代码所示当我们给两个线程设置相同的种子数的时候会发现每次产生的随机数也是相同的 // 创建两个线程
for (int i 0; i 2; i) {new Thread(() - {// 创建 Random 对象设置相同的种子Random random new Random(1024);// 生成 3 次随机数for (int j 0; j 3; j) {// 生成随机数int number random.nextInt();// 打印生成的随机数System.out.println(Thread.currentThread().getName() : number);// 休眠 200 mstry {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(---------------------);}}).start();
}
以上程序的执行结果为③ 线程安全问题当我们要使用一个类时我们首先关心的第一个问题是它是否为线程安全对于 Random 来说Random 是线程安全的。PS线程安全指的是在多线程的场景下程序的执行结果和预期的结果一致就叫线程安全的否则则为非线程安全的也叫线程安全问题。比如有两个线程第一个线程执行 10 万次 操作第二个线程执行 10 万次 -- 操作那么最终的结果应该是没加也没减如果程序最终的结果和预期不符则为非线程安全的。我们来看 Random 的实现源码public Random() {this(seedUniquifier() ^ System.nanoTime());
}public int nextInt() {return next(32);
}protected int next(int bits) {long oldseed, nextseed;AtomicLong seed this.seed;do {oldseed seed.get();nextseed (oldseed * multiplier addend) mask;} while (!seed.compareAndSet(oldseed, nextseed)); // CASCompare and Swap生成随机数return (int)(nextseed (48 - bits));
}
PS本文所有源码来自于 JDK 1.8.0_211。从以上源码可以看出Random 底层使用的是 CASCompare and Swap比较并替换来解决线程安全问题的因此对于绝大数随机数生成的场景使用 Random 不乏为一种很好的选择。PSJava 并发机制实现原子操作有两种一种是锁一种是 CAS。CAS 是 Compare And Swap比较并替换的缩写java.util.concurrent.atomic 中的很多类如AtomicInteger AtomicBoolean AtomicLong等都使用了 CAS 机制来实现。2.ThreadLocalRandomThreadLocalRandom 是 JDK 1.7 新提供的类它属于 JUCjava.util.concurrent下的一员为什么有了 Random 之后还会再创建一个 ThreadLocalRandom原因很简单通过上面 Random 的源码我们可以看出Random 在生成随机数时使用的 CAS 来解决线程安全问题的然而 CAS 在线程竞争比较激烈的场景中效率是非常低的原因是 CAS 对比时老有其他的线程在修改原来的值所以导致 CAS 对比失败所以它要一直循环来尝试进行 CAS 操作。所以在多线程竞争比较激烈的场景可以使用 ThreadLocalRandom 来解决 Random 执行效率比较低的问题。当我们第一眼看到 ThreadLocalRandom 的时候一定会联想到一次类 ThreadLocal确实如此。ThreadLocalRandom 的实现原理与 ThreadLocal 类似它相当于给每个线程一个自己的本地种子从而就可以避免因多个线程竞争一个种子而带来的额外性能开销了。① 基础使用接下来我们使用 ThreadLocalRandom 来生成一个 0 到 10 的随机数不包含 10实现代码如下// 得到 ThreadLocalRandom 对象
ThreadLocalRandom random ThreadLocalRandom.current();
for (int i 0; i 10; i) {// 生成 0-9 随机整数int number random.nextInt(10);// 打印结果System.out.println(生成随机数 number);
}
以上程序的执行结果为② 实现原理ThreadLocalRandom 的实现原理和 ThreadLocal 类似它是让每个线程持有自己的本地种子该种子在生成随机数时候才会被初始化实现源码如下public int nextInt(int bound) {// 参数效验if (bound 0)throw new IllegalArgumentException(BadBound);// 根据当前线程中种子计算新种子int r mix32(nextSeed());int m bound - 1;// 根据新种子和 bound 计算随机数if ((bound m) 0) // power of twor m;else { // reject over-represented candidatesfor (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// 获取当前线程中 threadLocalRandomSeed 变量然后在种子的基础上累加 GAMMA 值作为新种子// 再使用 UNSAFE.putLong 将新种子存放到当前线程的 threadLocalRandomSeed 变量中UNSAFE.putLong(t Thread.currentThread(), SEED,r UNSAFE.getLong(t, SEED) GAMMA); return r;
}
③ 优缺点分析ThreadLocalRandom 结合了 Random 和 ThreadLocal 类并被隔离在当前线程中。因此它通过避免竞争操作种子数从而在多线程运行的环境中实现了更好的性能而且也保证了它的线程安全。另外不同于 Random ThreadLocalRandom 明确不支持设置随机种子。它重写了 Random 的setSeed(long seed) 方法并直接抛出了 UnsupportedOperationException 异常因此降低了多个线程出现随机数重复的可能性。源码如下public void setSeed(long seed) {// only allow call from super() constructorif (initialized)throw new UnsupportedOperationException();
}
只要程序中调用了 setSeed() 方法就会抛出 UnsupportedOperationException 异常如下图所示ThreadLocalRandom 缺点分析虽然 ThreadLocalRandom 不支持手动设置随机种子的方法但并不代表 ThreadLocalRandom 就是完美的当我们查看 ThreadLocalRandom 初始化随机种子的方法 initialSeed() 源码时发现默认情况下它的随机种子也是以当前时间有关源码如下private static long initialSeed() {// 尝试获取 JVM 的启动参数String sec VM.getSavedProperty(java.util.secureRandomSeed);// 如果启动参数设置的值为 true则参数一个随机 8 位的种子if (Boolean.parseBoolean(sec)) {byte[] seedBytes java.security.SecureRandom.getSeed(8);long s (long)(seedBytes[0]) 0xffL;for (int i 1; i 8; i)s (s 8) | ((long)(seedBytes[i]) 0xffL);return s;}// 如果没有设置启动参数则使用当前时间有关的随机种子算法return (mix64(System.currentTimeMillis()) ^mix64(System.nanoTime()));
}
从上述源码可以看出当我们设置了启动参数“-Djava.util.secureRandomSeedtrue”时ThreadLocalRandom 会产生一个随机种子一定程度上能缓解随机种子相同所带来随机数可预测的问题然而默认情况下如果不设置此参数那么在多线程中就可以因为启动时间相同而导致多个线程在每一步操作中都会生成相同的随机数。3.SecureRandomSecureRandom 继承自 Random该类提供加密强随机数生成器。SecureRandom 不同于 Random它收集了一些随机事件比如鼠标点击键盘点击等SecureRandom 使用这些随机事件作为种子。这意味着种子是不可预测的而不像 Random 默认使用系统当前时间的毫秒数作为种子从而避免了生成相同随机数的可能性。基础使用// 创建 SecureRandom 对象并设置加密算法
SecureRandom random SecureRandom.getInstance(SHA1PRNG);
for (int i 0; i 10; i) {// 生成 0-9 随机整数int number random.nextInt(10);// 打印结果System.out.println(生成随机数 number);
}
以上程序的执行结果为SecureRandom 默认支持两种加密算法SHA1PRNG 算法提供者 sun.security.provider.SecureRandomNativePRNG 算法提供者 sun.security.provider.NativePRNG。当然除了上述的操作方式之外你还可以选择使用 new SecureRandom() 来创建 SecureRandom 对象实现代码如下SecureRandom secureRandom new SecureRandom();
通过 new 初始化 SecureRandom默认会使用 NativePRNG 算法来生成随机数但是也可以配置 JVM 启动参数“-Djava.security”参数来修改生成随机数的算法或选择使用 getInstance(算法名称) 的方式来指定生成随机数的算法。4.MathMath 类诞生于 JDK 1.0它里面包含了用于执行基本数学运算的属性和方法如初等指数、对数、平方根和三角函数当然它里面也包含了生成随机数的静态方法 Math.random() 此方法会产生一个 0 到 1 的 double 值如下代码所示。① 基础使用for (int i 0; i 10; i) {// 产生随机数double number Math.random();System.out.println(生成随机数 number);
}
以上程序的执行结果为② 扩展当然如果你想用它来生成一个一定范围的 int 值也是可以的你可以这样写for (int i 0; i 10; i) {// 生成一个从 0-99 的整数int number (int) (Math.random() * 100);System.out.println(生成随机数 number);
}
以上程序的执行结果为③ 实现原理通过分析 Math 的源码我们可以得知当第一次调用 Math.random() 方法时自动创建了一个伪随机数生成器实际上用的是 new java.util.Random()当下一次继续调用 Math.random() 方法时就会使用这个新的伪随机数生成器。源码如下public static double random() {return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}private static final class RandomNumberGeneratorHolder {static final Random randomNumberGenerator new Random();
}
总结本文我们介绍了 4 种生成随机数的方法其中 Math 是对 Random 的封装所以二者比较类似。Random 生成的是伪随机数是以当前纳秒时间作为种子数的并且在多线程竞争比较激烈的情况下因为要进行 CAS 操作所以存在一定的性能问题但对于绝大数应用场景来说使用 Random 已经足够了。当在竞争比较激烈的场景下可以使用 ThreadLocalRandom 来替代 Random但如果对安全性要求比较高的情况下可以使用 SecureRandom 来生成随机数因为 SecureRandom 会收集一些随机事件来作为随机种子所以 SecureRandom 可以看作是生成真正随机数的一个工具类。参考 鸣谢www.cnblogs.com/weink1215/p/4433790.htmlblog.csdn.net/lycyingO/article/details/95276195
往期推荐
ThreadLocal内存溢出代码演示和原因分析ThreadLocal不好用那是你没用对try-catch-finally中的4个巨坑老程序员也搞不定