应复用 random 实例而非频繁创建,避免因毫秒级种子重复导致序列相同;多线程下推荐使用 random.shared(.net 6+)或 threadlocal,密码学场景必须用 randomnumbergenerator。

Random 实例重复创建会导致序列重复
用 new Random() 在短时间内高频创建实例,比如在循环里或高并发请求中,会因种子相同导致生成一模一样的随机数序列。这是因为默认构造函数用系统时钟做种子,精度只有毫秒级。
正确做法是复用单个 Random 实例,比如声明为 static readonly 字段:
private static readonly Random _random = new Random();
注意:Random 不是线程安全的。多线程环境下直接共享实例可能返回 0 或异常值。可选方案包括:
- 用
lock包裹Next()调用(简单但有性能开销) - 每个线程用
ThreadLocal<random></random>(推荐,避免竞争) - 改用
Random.Shared(.NET 6+,线程安全且无锁)
RNGCryptoServiceProvider 适合密码学场景但更重
RNGCryptoServiceProvider(.NET Core 2.1+ 起被 RandomNumberGenerator 替代)生成的是密码学安全的随机字节,适用于密钥、token、盐值等不能被预测的场景。
但它比 Random 慢一个数量级,且不提供直接的整数/浮点数方法——必须自己把字节转成所需类型:
var bytes = new byte[4];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(bytes);
}
int value = BitConverter.ToInt32(bytes, 0) & 0x7FFFFFFF; // 转非负 int
常见误用是拿它去生成 UI 动画帧或游戏骰子点数——完全没必要,徒增开销。
Random.Next() 的边界陷阱:右边界永远不包含
Random.Next(int min, int max) 返回的是 [min, max) 区间,即 max 本身永远不会出现。这是最容易写错的地方。
比如想生成 1~6 的骰子点数,写成 rnd.Next(1, 6) 只会得到 1~5;正确写法是:
rnd.Next(1, 7) // [1, 7) → 1,2,3,4,5,6
其他变体也需注意:
-
Next()→[0, Int32.MaxValue](实际最大值是Int32.MaxValue - 1) -
NextDouble()→[0.0, 1.0),永远取不到 1.0
.NET 6+ 推荐优先用 Random.Shared
如果项目目标框架 ≥ .NET 6,Random.Shared 是最省心的选择:线程安全、无需手动管理生命周期、性能接近实例版 Random。
直接调用即可,不用 new,也不用担心并发问题:
int dice = Random.Shared.Next(1, 7);
但它仍不适用于密码学场景——安全性和 Random 完全一致,只是解决了线程安全这个最常见痛点。
真正需要不可预测性的场合,哪怕在 .NET 6+,也得回到 RandomNumberGenerator,别图省事。











