优先用 random 类;math.random() 仅限极简测试场景,因其不可控、易出错且不支持种子;多线程高频场景用 threadlocalrandom;安全敏感场景必须用 securerandom。

Java里该用 Random 还是 Math.random()?
直接说结论:日常生成随机整数、浮点数,优先用 Random 类;只在极简场景(比如一行临时测试)才考虑 Math.random()。前者可控、可复现、线程安全可选;后者固定返回 [0.0, 1.0) 的 double,还得手动缩放,还容易踩类型转换坑。
常见错误现象:Math.random() * 10 想取 0–9 的整数,结果偶尔得到 10(因为 Math.random() 理论上可能无限接近 1.0,乘完再强转 int 就越界);或者误以为它能指定种子,结果每次运行都不同、没法调试。
-
Random支持构造时传long种子,便于单元测试复现相同序列 -
Math.random()底层其实也用了Random单例,但封装太死,没法控制、没法重置 - 并发场景下,
Random实例不是线程安全的;高频多线程建议用ThreadLocalRandom,别共享一个Random
用 Random 生成指定范围的整数(含边界)
这是最常出错的地方:很多人写 rand.nextInt(max - min) + min,但漏了“含最大值”这个需求——nextInt(bound) 的 bound 是**不包含**的上限。
比如要 1–6(骰子),不能写 rand.nextInt(6) + 1 —— 这是对的;但若要 0–10(含 10),写 rand.nextInt(10) + 0 就错了,会漏掉 10。
立即学习“Java免费学习笔记(深入)”;
- 正确公式:
rand.nextInt(max - min + 1) + min(前提是min ≤ max) - 注意整数溢出风险:如果
max很大(如Integer.MAX_VALUE),max - min + 1可能溢出;此时应改用ThreadLocalRandom.current().nextInt(min, max + 1),它内部做了安全处理 - 别用
(int)(Math.random() * (max - min + 1)) + min—— 强制截断可能引入轻微偏差,且不可控
ThreadLocalRandom 什么时候必须用?
当你的随机数生成发生在多线程高频调用路径里(比如 Web 请求中的 ID 生成、负载均衡选节点),就该换 ThreadLocalRandom。它为每个线程维护独立实例,避免 Random 内部的 CAS 竞争开销。
典型错误:在静态工具类里持有一个全局 Random 实例,被上百个线程同时调用 nextInt(),性能反而比单线程还差。
- 用法很简单:
ThreadLocalRandom.current().nextInt(1, 7)(生成 1–6) - 它没有 public 构造器,也不支持设种子——设计目标就是“快+隔离”,不是“可复现”
- 不要在循环里反复调用
ThreadLocalRandom.current();虽然开销小,但缓存到局部变量更干净:ThreadLocalRandom r = ThreadLocalRandom.current();
SecureRandom 不是“更随机”,而是“更难预测”
如果你在生成密码盐、JWT 密钥、一次性验证码,就必须用 SecureRandom。它不比 Random “随机性更强”,而是使用操作系统级熵源(如 /dev/urandom),抗预测、抗重放。
常见误解:以为加了 Secure 前缀就能让游戏掉落更“公平”——完全没必要,还拖慢百倍。
- 初始化可能阻塞(尤其 Linux 上首次读
/dev/random);生产环境务必配成new SecureRandom().getInstance("SHA1PRNG")或明确指定"NativePRNGNonBlocking" - 它也支持种子,但传入的种子会被混合进系统熵,不能靠它复现序列
- 别用
Math.random()或普通Random生成 token——攻击者只要知道时间戳和算法,几秒就能穷举出来
事情说清了就结束。真正要注意的,从来不是“怎么写那一行代码”,而是你手里的随机数,到底要扛住人眼观察、单元测试,还是黑客的暴力破解——选错类,后面全是补丁。










