C# Random类的正确应用方法
作者:gt1987 发布时间:2021-06-25 14:56:17
Random类介绍
Random类一个用于产生 伪随机 数字的类。这里的伪随机表示有随机性但是可以基于算法模拟出随机规律。
Random类的构造方式有两种。
Random r= new Random()。
会以当前系统时间作为默认种子构建一个随机序列Random r = new Random(unchecked((int)DateTime.Now.Ticks));。
自定义一个种子,通常会使用时间Ticks。
随机性保证
由于Random的 伪随机 性,所以如果多个Random随机序列生成的时间间隔很短(官方说法15ms内),那么他们产生的随机数会大概率相同。如下列代码
/// <summary>
/// 错误的Random构建。
/// </summary>
public static void Bad_Random()
{
//正确做法应当将 Random构建防止循环外。
//Random创建间隔时间极短的情况下,随机算法序列会基本一致,倒是随机性也是一致的
//var r = new Random();
for (int i = 0; i < 10; i++)
{
var r = new Random();
var val = r.Next(1, 100);
Console.WriteLine(val);
}
}
运行结果:
所以在生产中通常可以考虑将Random单例化,以保证其随机算法的序列独一性。这也是官方推荐的方式。
Instead of instantiating individual Random objects, we recommend that you create a single Random instance to generate all the random numbers needed by your app.
这个问题在.net core下官方组件已对Random的构建作优化,所以上面的案例代码如果放在.net core项目下运行,你会发现可以正确的生成随机数。有兴趣的小伙伴可以自己尝试一下。不过为了代码的延续性,还是建议Random作为单例模式设计。
那么将Random设计为单例是否就解决了随机性的问题了呢,这时候就涉及到另外一个问题,Random不是线程安全的。如下列代码
/// <summary>
/// 生成一个10位随机数
/// 设定了一定的复杂性,保证单线程下随机数不重复
/// </summary>
/// <param name="random">Random.</param>
/// <returns>随机数.</returns>
private static string GenerateRandomStr(Random random)
{
string source = "ABCDEFGHIKLMNOPQRTUVWXYZabcdefghiklmnopqrtuvwxyz";
int length = 10;
var list = Enumerable.Repeat(source, length)
.Select(s => s[random.Next(s.Length)]).ToArray();
return new string(list);
}
/// <summary>
/// 单线程基本可以保证唯一性
/// </summary>
public static void Good_Random_In_SingleThread()
{
//正确做法应当将 Random构建防止循环外。
//Random创建间隔时间极短的情况下,随机算法序列会基本一致,倒是随机性也是一致的
var r = new Random();
ConcurrentBag<string> list = new ConcurrentBag<string>();
for (int i = 0; i < 20000; i++)
{
var val = GenerateRandomStr(r);
list.Add(val);
}
Console.WriteLine($"单线程下重复数据有:{20000 - list.Distinct().Count()}");
}
/// <summary>
/// 多线程下的Random构建。
/// Bad案例,Random非线程安全
/// 多线程高并 * 况下,会出现概率重复
/// </summary>
public static void Bad_Random_In_MultThreads()
{
var r = new Random(unchecked((int)DateTime.Now.Ticks));
ConcurrentBag<string> list = new ConcurrentBag<string>();
var t1 = Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
var val = GenerateRandomStr(r);
list.Add(val);
}
});
var t2 = Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
var val = GenerateRandomStr(r);
list.Add(val);
}
});
Task.WaitAll(t1, t2);
Console.WriteLine($"线程1和线程2的重复数据有:{20000 - list.Distinct().Count()}");
}
运行结果:
这种重复率在生产环境上是不可接受的。那么产生的原因是什么呢?根源还是在 伪随机 和 线程不安全 上。我们可以想象下,一个Random实例中基于随机算法产生的一个随机数序列,在单线程下pop出一个随机数,然后指向下一个随机数。而在高并发的多线程情况下,指向下一个随机数的动作还未完成时,另一个线程又来请求pop,这样相同的随机数被重复pop了。
网上有很多多线程下Random的解决方案,我查阅了一些感觉都不是很好。以下是我的解决方案。用到了 ThreadLocal
。这个类详细的作用大家可以自己去查阅,这里大家只需要知道这个类可以保证它包含的对象只能线程内独享。简单说,同一类型对象 每个线程都独有一个Random实例互不影响。
//利用ThreadLocal 实现每个线程下Random独有
//再通过seed原子性变更,保证每个Random的seed不同而生成的随机数列也不同
private static int seed = 100;
private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed)));
/// <summary>
/// 多线程下的Random构建。
/// </summary>
public static void Good_Random_In_MultThreads()
{
ConcurrentBag<string> list = new ConcurrentBag<string>();
var t1 = Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
var val = GenerateRandomStr(threadLocal.Value);
list.Add(val);
}
});
var t2 = Task.Run(() =>
{
for (int i = 0; i < 10000; i++)
{
var val = GenerateRandomStr(threadLocal.Value);
list.Add(val);
}
});
Task.WaitAll(t1, t2);
Console.WriteLine($"[ThreadLocal模式]线程1和线程2的重复数据有:{20000 - list.Distinct().Count()}");
}
运行结果:
由此可见,基于ThreadLocal的特性,并区别了每个线程下的seed都不一样,从而保证每个Random的随机性也不行一样。
那么到这里Random的随机性问题解决了吗??
再深入思考下,对于集群部署情况,多台服务器同时运行,上述的Random随机性能保证吗?聪明的小伙伴应该能想到在不同服务器上,由于初始seed相同,可能又导致Random的随机性相同的情况发生。
那么解决方案也很简单,保证每台服务器的初始seed不同即可。这里的解决方案很多,不限于机器编号、IP地址后几位、启动时间(Environment.TickCount)等等。
这样,到这里Random的随机性问题终于可以告一段落了。
来源:https://www.cnblogs.com/gt1987/p/14034660.html


猜你喜欢
- 前言Kotlin并没有想象中的那么牛逼哄哄,也并不难,我更喜欢把他看做一枚语法糖,所谓的语法糖就是:能够让代码变得更加简单易读的辅助工具。而
- 首先我们要做的就是先把IIS(Internet信息服务)打开,我用的是win8 的系统,所以这里以win8系统的操作来讲一、IIS的一些事先
- Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsof
- 一. 依赖管理Ⅰ. 部分dependency导入时为啥不需要指定版本?我们创建项目时添加的依赖并没有帮我们指定版本号<>,那Sp
- 本文实例为大家分享了C#支付宝扫码支付示的具体代码,供大家参考,具体内容如下支付宝工具类using System; using System
- SpringBoot自定义 * 和跨域配置冲突技术栈vue-cli3,springboot 2.3.2.RELEASE问题引出在做毕业设计过
- Spring 配置文件报错:元素 "context:component-scan" 的前缀 "context&
- 迭代器是一种模式,它可以使得对于序列类型的数据结构的遍历行为与被遍历的对象分离,即我们无需关心该序列的底层结构是什么样子的。只要拿到这个对象
- 一、引言在许多编程语言中,都有函数回调这一概念。C 和 C++ 中有函数指针,因此可以将函数作为参数传给其它函数,以便过后调用。而在 Jav
- windows下使用cmd命令提示符生成java webservice客户端代码,可以使用命令提示符直接生成客户端代码,直接导入到项目中,只
- 前言通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各
- this可能是几乎所有有一点面向对象思想的语言都会引用到的变量,java自然不例外。只是,this有多少种用法,我也不知道了,让我们来see
- 最近在使用springboot过程中用到了mybatis-plus ,springboot版本是2.3.1.RELEASE,mybatis-
- 一、WebSocket简介WebSocket协议通过在客户端和服务端之间提供全双工通信来进行Web和服务器的交互功能。在WebSocket应
- 情景模式的设置大家应当相当熟悉了,但是在Android中如何通过自己的程序进行情景模式的设置呢,情景模
- 本文实例讲述了Android编程自定义View时添加自己的 * 。分享给大家供大家参考,具体如下: * 在Java中非常常用,在自定义控件时
- 本文实例讲述了java实现List中对象排序的方法。分享给大家供大家参考,具体如下:package com.test; import jav
- 一个界面,实现在向页面添加图片时,在标题上显示一个水平进度条,当图片载入完毕后,隐藏进度条并显示图片具体实现方法:res/layout/ma
- 依赖SpringBoot版本:2.4.2 <dependencies> &
- 本人一直使用的是Eclipse作为开发工具的,不过现在IDEA非常的受推崇,所以决定上手试一试。网上有很多旗舰版的文章,我没有仔细看,我这次