2025年生成范围内的随机整数(生成一个范围的随机数)

生成范围内的随机整数(生成一个范围的随机数)p class f center img src http dingyue ws 126 net 2021 0617 ca054e53g00q gif br p p 作者 Java 中文社群 责编 p

大家好,我是讯享网,很高兴认识大家。




讯享网

                <p class="f_center"><img src="http://dingyue.ws.126.net/2021/0617/ca054e53g00qutrng000hd200hs0028g00hs0028.gif"/><br/></p><p>作者 | Java中文社群 责编 | 梦依丹</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F10bd9834j00qutrng0022d200u000k1g00it00cj.jpg&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p>在 Java 中,生成随机数的场景有很多,所以本文我们就来盘点一下 4 种生成随机数的方式,以及它们之间的区别和每种生成方式所对应的场景。</p><p><br/><strong>Random</strong><br/></p><p>Random 类诞生于 JDK 1.0,它产生的随机数是伪随机数,也就是有规则的随机数。Random 使用的随机算法为 linear congruential pseudorandom number generator (LGC) 线性同余法伪随机数。在随机数生成时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。</p><p>Random 对象在种子数相同的情况下,相同次数生成的随机数是相同的。比如两个种子数相同的 Random 对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。默认情况下 new Random() 使用的是当前纳秒时间作为种子数的。</p><p><strong>① 基础使用</strong></p><p>使用 Random 生成一个从 0 到 10 的随机数(不包含 10),实现代码如下:</p><p><br/><br/>以上程序的执行结果为:<br/></p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F8005d813p00qutrnh0006d200a900i9g00a900i9.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p><strong>② 优缺点分析</strong></p><p>Random 使用 LGC 算法生成伪随机数的优点是执行效率比较高,生成的速度比较快。</p><p>它的缺点是如果 Random 的随机种子一样的话,每次生成的随机数都是可预测的(都是一样的)。如下代码所示,当我们给两个线程设置相同的种子数的时候,会发现每次产生的随机数也是相同的:</p><p><br/><br/>以上程序的执行结果为:<img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F80168266p00qutrnh000gd200h100jtg00h100jt.png&thumbnail=660x2147483647&quality=80&type=jpg"/><strong>③ 线程安全问题</strong></p><p>当我们要使用一个类时,我们首先关心的第一个问题是:它是否为线程安全?对于 Random 来说,Random 是线程安全的。</p><p><blockquote>PS:线程安全指的是在多线程的场景下,程序的执行结果和预期的结果一致,就叫线程安全的,否则则为非线程安全的(也叫线程安全问题)。比如有两个线程,第一个线程执行 10 万次 ++ 操作,第二个线程执行 10 万次 -- 操作,那么最终的结果应该是没加也没减,如果程序最终的结果和预期不符,则为非线程安全的。</blockquote></p><p>我们来看 Random 的实现源码:</p><p><br/><blockquote>PS:本文所有源码来自于 JDK 1.8.0_211。</blockquote></p><p>从以上源码可以看出,Random 底层使用的是 CAS(Compare and Swap,比较并替换)来解决线程安全问题的,因此对于绝大数随机数生成的场景,使用 Random 不乏为一种很好的选择。</p><p><blockquote>PS:Java 并发机制实现原子操作有两种:一种是锁,一种是 CAS。 CAS 是 Compare And Swap(比较并替换)的缩写,java.util.concurrent.atomic 中的很多类,如(AtomicInteger AtomicBoolean AtomicLong等)都使用了 CAS 机制来实现。</blockquote></p><p><br/><strong>ThreadLocalRandom</strong><br/></p><p>ThreadLocalRandom 是 JDK 1.7 新提供的类,它属于 JUC(java.util.concurrent)下的一员,为什么有了 Random 之后还会再创建一个 ThreadLocalRandom?</p><p>原因很简单,通过上面 Random 的源码我们可以看出,Random 在生成随机数时使用的 CAS 来解决线程安全问题的,然而 CAS 在线程竞争比较激烈的场景中效率是非常低的,原因是 CAS 对比时老有其他的线程在修改原来的值,所以导致 CAS 对比失败,所以它要一直循环来尝试进行 CAS 操作。所以在多线程竞争比较激烈的场景可以使用 ThreadLocalRandom 来解决 Random 执行效率比较低的问题。</p><p>当我们第一眼看到 ThreadLocalRandom 的时候,一定会联想到一次类 ThreadLocal,确实如此。ThreadLocalRandom 的实现原理与 ThreadLocal 类似,它相当于给每个线程一个自己的本地种子,从而就可以避免因多个线程竞争一个种子,而带来的额外性能开销了。</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F6aca71f4p00qutrni003od200u000gwg00id00ac.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/><strong>① 基础使用</strong><br/></p><p>接下来我们使用 ThreadLocalRandom 来生成一个 0 到 10 的随机数(不包含 10),实现代码如下:</p><p><br/><br/>以上程序的执行结果为:</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2Fbd8f15c7p00qutrni0006d200a400i0g00a400i0.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p><strong>② 实现原理</strong></p><p>ThreadLocalRandom 的实现原理和 ThreadLocal 类似,它是让每个线程持有自己的本地种子,该种子在生成随机数时候才会被初始化,实现源码如下:</p><p><br/><br/><strong>③ 优缺点分析</strong></p><p>ThreadLocalRandom 结合了 Random 和 ThreadLocal 类,并被隔离在当前线程中。因此它通过避免竞争操作种子数,从而在多线程运行的环境中实现了更好的性能,而且也保证了它的线程安全。</p><p>另外,不同于 Random, ThreadLocalRandom 明确不支持设置随机种子。它重写了 Random 的setSeed(long seed) 方法并直接抛出了 UnsupportedOperationException 异常,因此降低了多个线程出现随机数重复的可能性。</p><p>源码如下:</p><p><br/><br/></p><p>只要程序中调用了 setSeed() 方法就会抛出 UnsupportedOperationException 异常,如下图所示:</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F204ef8b0p00qutrnj001bd200u000bog00id0075.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p><strong>ThreadLocalRandom 缺点分析</strong></p><p>虽然 ThreadLocalRandom 不支持手动设置随机种子的方法,但并不代表 ThreadLocalRandom 就是完美的,当我们查看 ThreadLocalRandom 初始化随机种子的方法 initialSeed() 源码时发现,默认情况下它的随机种子也是以当前时间有关,源码如下:</p><p><br/><br/></p><p>从上述源码可以看出,当我们设置了启动参数“-Djava.util.secureRandomSeed=true”时,ThreadLocalRandom 会产生一个随机种子,一定程度上能缓解随机种子相同所带来随机数可预测的问题,然而默认情况下如果不设置此参数,那么在多线程中就可以因为启动时间相同,而导致多个线程在每一步操作中都会生成相同的随机数。</p><p><strong>SecureRandom</strong></p><p>SecureRandom 继承自 Random,该类提供加密强随机数生成器。SecureRandom 不同于 Random,它收集了一些随机事件,比如鼠标点击,键盘点击等,SecureRandom 使用这些随机事件作为种子。这意味着,种子是不可预测的,而不像 Random 默认使用系统当前时间的毫秒数作为种子,从而避免了生成相同随机数的可能性。</p><p>基础使用<br/><br/>以上程序的执行结果为:</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2Fee07bf98p00qutrnk0006d200a700i6g00a700i6.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p>SecureRandom 默认支持两种加密算法:</p><p><ol><li></p><p>SHA1PRNG 算法,提供者 sun.security.provider.SecureRandom;</p><p></li><li></p><p>NativePRNG 算法,提供者 sun.security.provider.NativePRNG。</p><p></li></ol></p><p>当然除了上述的操作方式之外,你还可以选择使用 new SecureRandom() 来创建 SecureRandom 对象,实现代码如下:</p><p><br/><br/></p><p>通过 new 初始化 SecureRandom,默认会使用 NativePRNG 算法来生成随机数,但是也可以配置 JVM 启动参数“-Djava.security”参数来修改生成随机数的算法,或选择使用 getInstance("算法名称") 的方式来指定生成随机数的算法。</p><p><br/><strong>Math</strong></p><p>Math 类诞生于 JDK 1.0,它里面包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数,当然它里面也包含了生成随机数的静态方法 Math.random() ,此方法会产生一个 0 到 1 的 double 值,如下代码所示。</p><p><strong>① 基础使用</strong><br/><br/></p><p class="f_center">以上程序的执行结果为:<img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F1661c5d6p00qutrnk0015d200lm00iqg00id00fw.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p><strong>② 扩展</strong></p><p>当然如果你想用它来生成一个一定范围的 int 值也是可以的,你可以这样写:</p><p><br/><br/></p><p class="f_center">以上程序的执行结果为:<img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0617%2F71bc829dp00qutrnk0008d200b300ijg00b300ij.png&thumbnail=660x2147483647&quality=80&type=jpg"/><br/></p><p><strong>③ 实现原理</strong></p><p>通过分析 Math 的源码我们可以得知:当第一次调用 Math.random() 方法时,自动创建了一个伪随机数生成器,实际上用的是 new java.util.Random(),当下一次继续调用 Math.random() 方法时,就会使用这个新的伪随机数生成器。</p><p>源码如下:</p><p><br/></p><p><strong>总结</strong></p><p>本文我们介绍了 4 种生成随机数的方法,其中 Math 是对 Random 的封装,所以二者比较类似。Random 生成的是伪随机数,是以当前纳秒时间作为种子数的,并且在多线程竞争比较激烈的情况下因为要进行 CAS 操作,所以存在一定的性能问题,但对于绝大数应用场景来说,使用 Random 已经足够了。当在竞争比较激烈的场景下可以使用 ThreadLocalRandom 来替代 Random,但如果对安全性要求比较高的情况下,可以使用 SecureRandom 来生成随机数,因为 SecureRandom 会收集一些随机事件来作为随机种子,所以 SecureRandom 可以看作是生成真正随机数的一个工具类。</p><p><strong>参考 &amp; 鸣谢:</strong></p><p><ul><li></p><p>www.cnblogs.com/weink1215/p/4433790.html</p><p></li><li></p><p>blog.csdn.net/lycyingO/article/details/95276195</p><p></li></ul></p>

讯享网
小讯
上一篇 2025-04-25 19:15
下一篇 2025-04-21 10:42

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/184003.html