Java中生成随机数的4种方式!

article/2025/10/3 9:28:19

在 Java 中,生成随机数的场景有很多,所以本文我们就来盘点一下 4 种生成随机数的方式,以及它们之间的区别和每种生成方式所对应的场景。

1.Random

Random 类诞生于 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);
}
复制代码

以上程序的执行结果为: image.png

② 优缺点分析

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();
}
复制代码

以上程序的执行结果为:

image.png

③ 线程安全问题

当我们要使用一个类时,我们首先关心的第一个问题是:它是否为线程安全?对于 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)); // 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 机制来实现。

2.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);
}
复制代码

以上程序的执行结果为: image.png

② 实现原理

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 异常,如下图所示: image.png

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 会产生一个随机种子,一定程度上能缓解随机种子相同所带来随机数可预测的问题,然而默认情况下如果不设置此参数,那么在多线程中就可以因为启动时间相同,而导致多个线程在每一步操作中都会生成相同的随机数

3.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);
}
复制代码

以上程序的执行结果为: image.png SecureRandom 默认支持两种加密算法:

  1. SHA1PRNG 算法,提供者 sun.security.provider.SecureRandom;
  2. NativePRNG 算法,提供者 sun.security.provider.NativePRNG。

当然除了上述的操作方式之外,你还可以选择使用 new SecureRandom() 来创建 SecureRandom 对象,实现代码如下:

SecureRandom secureRandom = new SecureRandom();
复制代码

通过 new 初始化 SecureRandom,默认会使用 NativePRNG 算法来生成随机数,但是也可以配置 JVM 启动参数“-Djava.security”参数来修改生成随机数的算法,或选择使用 getInstance("算法名称") 的方式来指定生成随机数的算法。

4.Math

Math 类诞生于 JDK 1.0,它里面包含了用于执行基本数学运算的属性和方法,如初等指数、对数、平方根和三角函数,当然它里面也包含了生成随机数的静态方法 Math.random()此方法会产生一个 0 到 1 的 double 值,如下代码所示。

① 基础使用

for (int i = 0; i < 10; i++) {// 产生随机数double number = Math.random();System.out.println("生成随机数:" + number);
}
复制代码

以上程序的执行结果为: image.png

② 扩展

当然如果你想用它来生成一个一定范围的 int 值也是可以的,你可以这样写:

for (int i = 0; i < 10; i++) {// 生成一个从 0-99 的整数int number = (int) (Math.random() * 100);System.out.println("生成随机数:" + number);
}
复制代码

以上程序的执行结果为: image.png

③ 实现原理

通过分析 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 可以看作是生成真正随机数的一个工具类。


作者:Java中文社群
链接:https://juejin.cn/post/6973816615209009159
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


http://chatgpt.dhexx.cn/article/18pJB7X5.shtml

相关文章

谈论SQL注入攻击的重要性

"SQL注入”是一种利用未过滤/未审核用户输入的攻击方法&#xff08;“缓存溢出”和这个不同&#xff09;&#xff0c;意思就是让应用运行本不应该运行的SQL代码。黑客或者恶搞的用户&#xff0c;利用了程序开发人员在开发的时候没有对SQL进行严格的处理而造成的漏洞&#…

【SQL注入攻击介绍】

目录 前言 本质和危害 分类 注入一般步骤 注入实战 前言 sql注入一直以来都稳居owasp-top10榜首&#xff0c;近年来更是爆出很多的数据库泄露攻击事件&#xff0c;如最近上海某公安存在数据库泄露事件。今天简单的分析以下sql注入的一些特性和方式&#xff1a; owasp-t…

SQL注入攻击与防护

目录 一、SQL注入攻击概述 1.1 SQL注入概念 1.1.1 标准查询过程 1.1.2 SQL注入定义 1.2 SQL注入根本原因 1.3 SQL注入条件 1.4 SQL注入防范 1.4.1 根本原因&#xff1a;过滤不严 1.4.2 安全设计原则&#xff1a;数据与代码分离 1.5 SQL注入流程 1.6 SQL注入分类 1.…

使用日志进行调查 - SQL 注入攻击示例

日志文件是服务器提供的非常有价值的信息。几乎所有服务器、服务和应用程序都提供某种日志记录。日志文件记录在服务或应用程序运行期间发生的事件和操作。 日志文件为我们提供了服务器行为的精确视图以及关键信息&#xff0c;例如何时、如何以及由谁访问服务器。此类信息可以…

Web—SQL注入攻击

文章目录 一、mysql常用语句二、SQL注入概念1. 产生原因2. 攻击分类 三、攻击流程1. 常用检测语句如何识别SQL注入2. Mysql注入常用函数3. 查询数据的核心语法4. 联合查询5. 报错注入6. 布尔盲注7. 时间盲注8. SQL注入爆库语句9. Sqlmap常用命令 四、常见防护手段及绕过方式1. …

DVWA SQL注入攻击

SQL注入原理 SQL注入就是通过SQL命令插入到web表单递交或输入域名页面请求的查询字符串&#xff0c;最终达到欺骗服务器执行恶意的SQL命令。具体来说&#xff0c;它是利用现有应用程序&#xff0c;将恶意的SQL命令注入到后台数据库引擎执行的能力&#xff0c;它可以通过在WEB表…

sql注入攻击实例mysql_SQL 注入攻击案例

一、检测注入点 二、判断是否存在 SQL 注入可能 三、数据库爆破 四、字段爆破 五、数据库表爆破 六、用户名、密码爆破 七、总结 一、检测注入点 首先,在 http://120.203.13.75:6815/?id=1 目标站点页面发现了 ?id,说明可以通过查询 id=1 的内容来获得页面。 这相当于查询语…

SQL注入攻击实战演示(附源码)

SQL注入是一种非常常见的数据库攻击手段&#xff0c;SQL注入漏洞也是网络世界中最普遍的漏洞之一。大家也许都听过某某学长通过攻击学校数据库修改自己成绩的事情&#xff0c;这些学长们一般用的就是SQL注入方法。 文章目录&#xff1a; 何谓SQL注入&#xff1f; SQL数据库操…

SQL注入攻击入门

目录 一、SQL注入的原理 SQL注入漏洞的条件 二、SQL注入的危害 三、SQL注入的分类 1、注入点数据类型分类 &#xff08;1&#xff09;数字型注入 &#xff08;2&#xff09;字符型注入 2、注入点位置分类 3、注入方法分类 &#xff08;1&#xff09;布尔型注入 &…

数学里上凹,下凹,上凸,下凸

https://zhidao.baidu.com/question/238541854.html 数学里上凹&#xff0c;下凹&#xff0c;上凸&#xff0c;下凸统称为曲线的凸知性&#xff0c;其是指在平面坐标系里的图形样式&#xff1a; 1、开口向上的曲线&#xff0c;称为上凹&#xff0c;或称为下凸&#xff0c;形状…

凸凹函数

凹凸函数的代数定义 若函数f(x)为凸函数&#xff0c;那么- f(x) 为凹函数。所以&#xff0c;清楚了凸函数&#xff0c;等价于清楚了凹函数 凸函数的定义 对于一元函数f(x)&#xff0c;如果对于任意tϵ[0,1]均满足&#xff1a;f(tx1(1−t)x2)≤tf(x1)(1−t)f(x2)&#xff0c;则称…

一元函数的凹性concavity以及二阶导数

凹性&#xff08;concavity&#xff09;与函数导数的变化率有关。一个函数f是上凹(即抛物线方向开口向上)&#xff0c;其导数f是增函数&#xff0c;也意味着f的导数&#xff08;即f&#xff09;是正数&#xff1b;类似的一个函数f是下凹(即抛物线方向开口向下)&#xff0c;其导…

转载--32个鲜为人知的自学网站

分享32个鲜为人知并且完全免费的高质量自学网站&#xff0c;每个都是堪称神器&#xff0c;让你相见恨晚。 1&#xff1a;Oeasy 是一个完全免费的综合视频教程网站&#xff0c;非常良心实用。 它提供的视频教程非常丰富并且质量很高&#xff0c;包括&#xff1a;PS 教程、手机摄…

分享32个高质量的自学网站

原文作者&#xff1a;木子淇 链接&#xff1a;有哪些高质量的自学网站&#xff1f; - 知乎 来源&#xff1a;知乎 1&#xff1a;Oeasy http://oeasy.org/Oeasy 是一个完全免费的综合视频教程网站&#xff0c;非常良心实用。 它提供的视频教程非常丰富并且质量很高&#xf…

Jack魏--个人介绍

Jack魏 1.名字由来2.兴趣爱好3.专业技能4.所获荣誉5.所获证书6.博主平台7.阅读书籍 1.名字由来 大二去韩国大邱永进专门大学短期学习的时候&#xff0c; 遇到了来自加拿大的Jack老师&#xff0c; 他无拘无束、有趣的教学方式感动了我&#xff0c; 所以我的英文名为了致敬他&am…

saiku 连接 MySQL_Saiku连接mysql数据库(二)

Saiku连接Mysql数据库展示数据 参考链接&#xff1a;https://www.cnblogs.com/shirui/p/8573491.html 前提&#xff1a;Saiku已安装好&#xff0c;mysql已安装好 1.添加Saiku的数据库驱动&#xff1a; mysql-connect-java-5.1.17.jar 下载相应的数据库驱动放到 saiku-server\to…

解决生成的saiku文件读取中文乱码的问题

javax.ws.rs.WebApplicationException: org.saiku.service.util.exception.SaikuServiceException: org.saiku.service.util.exception.SaikuServiceException: Cannot get native cube for cube ( [pda].[pdaTest].[pdaTest].[鑽簵鍒嗘瀽] ) at org.saiku.web.rest.resource…

saiku集成CAS

最近一直在研究saiku如何集成公司的单点登录系统&#xff0c;通过google、百度搜集查阅了大量的相关资料&#xff0c;并和网友讨论最终在公司内部集成完成&#xff0c;最终测试上线&#xff1b;现将这一过程中的经验记录下来&#xff0c;分享给需要的朋友.... saiku集成CAS这一…

saiku连mysql 使用_Saiku的基本使用介绍(三)

Saiku的基本使用介绍(这里都是使用Admin用户登录系统) 1、启动安装好的Saiku ( ./start-saiku.sh ) ,浏览器使用访问系统 http://localhost:8080 ,然后使用 admin admin 登录系统 2、 登录系统后&#xff0c;首先点击 A &#xff0c;进入管理控制平台(添加数据源信息) 2.1 根据…