我们来看 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)); // CAS(Compare and Swap)生成随机数 return (int)(nextseed >>> (48 - bits));}
PS:本文所有源码来自于 JDK 1.8.0_211。
从以上源码可以看出,Random 底层使用的是 CAS(Compare and Swap,比较并替换)来解决线程安全问题的,因此对于绝大数随机数生成的场景,使用 Random 不乏为一种很好的选择。
PS:Java 并发机制实现原子操作有两种:一种是锁,一种是 CAS。
CAS 是 Compare And Swap(比较并替换)的缩写,java.util.concurrent.atomic 中的很多类,如(AtomicInteger AtomicBoolean AtomicLong等)都使用了 CAS 机制来实现。
ThreadLocalRandom
ThreadLocalRandom 是 JDK 1.7 新提供的类,它属于 JUC(java.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) thrownew IllegalArgumentException(BadBound); // 根据当前线程中种子计算新种子 int r = mix32(nextSeed()); int m = bound - 1; // 根据新种子和 bound 计算随机数 if ((bound & m) == 0) // power of two r &= m; else { // reject over-repsented candidates for (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() constructor if (initialized) thrownew 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.secureRandomSeed=true”时,ThreadLocalRandom 会产生一个随机种子,一定程度上能缓解随机种子相同所带来随机数可预测的问题,然而默认情况下如果不设置此参数,那么在多线程中就可以因为启动时间相同,而导致多个线程在每一步操作中都会生成相同的随机数。
SecureRandom
SecureRandom 继承自 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);}
本文地址:百科问答频道 https://www.neebe.cn/wenda/903352_2.html,易企推百科一个免费的知识分享平台,本站部分文章来网络分享,本着互联网分享的精神,如有涉及到您的权益,请联系我们删除,谢谢!