做抽奖接口,用 new Random().nextInt(10),每个请求都 new 一个对象,压测时发现同一毫秒进来的请求中了同样的奖。java随机数生成器看着简单,种子和时间挂上钩就出问题。

Random 的种子机制

Random 默认拿当前时间做种子,两个实例在极短间隔内创建,序列可能一模一样。正确做法是把 Random 提成成员变量复用,或者直接用 ThreadLocalRandom。单线程下 nextInt(100) 生成 0 到 99 的整数,区间左闭右开。nextInt(0) 会抛 IllegalArgumentException,范围来自变量时先校验一下。

多线程用 ThreadLocalRandom

并发场景写 ThreadLocalRandom.current().nextInt(1, 101),得到 1 到 100。nextInt(a, b) 的上限不包含在内,要取到 b 就得加一,这个细节写错就是少一个值。它按线程维护种子,争用比共享一个 Random 少很多。

安全场景换 SecureRandom

生成验证码、令牌、抽奖种子这类东西,用 SecureRandom,速度慢一些但不可预测。普通业务的随机排序、造测试数据用 Random 就够了,没必要一刀切全上 SecureRandom,性能会白白浪费。想复现某次抽奖结果,可以固定种子 new Random(42),测试环境里很常用。

几个常用写法

Math.random() 返回 0 到 1 之间的 double,乘 100 再取整能得到 0 到 99。JDK 8 之后 Random 还有 ints、doubles 这些流式方法,配合 limit 一次取一批。洗牌用 Collections.shuffle,比自己写交换逻辑稳。生成不重复的抽奖名单,先把候选集洗牌再按顺序取,比循环判重简单得多。

java随机数这块的判断标准:单线程普通场景用 Random 并复用实例,多线程用 ThreadLocalRandom,涉及安全用 SecureRandom。抽奖业务最好在种子里加点业务维度,别只靠时间。