C# Random类的正确应用方法

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的随机性问题终于可以告一段落了。

到此这篇关于C# Random类的正确应用方法的文章就介绍到这了,更多相关C# Random类内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C#学习笔记- 随机函数Random()的用法详解

    Random.Next() 返回非负随机数: Random.Next(Int) 返回一个小于所指定最大值的非负随机数 Random.Next(Int,Int) 返回一个指定范围内的随机数,例如(-100,0)返回负数 1.random(number)函数介绍 见帮助文档,简单再提一下,random(number)返回一个0~number-1之间的随机整数.参数number代表一个整数. 示例: trace(random(5)); 2.Math.random() 见帮助文档.返回一个有14位精度的

  • c# Random快速连续产生相同随机数的解决方案

    代码如下: 复制代码 代码如下: namespace RandomTest { class Program { static void Main(string[] args) { for (int i = 0; i < 100; i++) { Random d = new Random(); Console.WriteLine(d.Next(100)); } } } } 理论上而言,这个程序会产生100个不同的0-100的整数,而实际情况却是除了第一个数字不同外,剩余99个数字会产生随机的99个

  • C#利用Random得随机数求均值、方差、正态分布的方法

    本文实例讲述了C#利用Random得随机数求均值.方差.正态分布的方法.分享给大家供大家参考.具体如下: 最近在做中小学试卷分析系统,其中数据的分析让自己很头疼,整个系统采用B/S架构.在分析试卷难度梯度的时候需要用到正态分布,自己做了一些,也查阅了一些资料,终于掌握了将一组数据分析检验,最后生成正态分布. (1)利用随机函数rand()生成(0,1)区间的100个均匀分布随机数: (2)计算这100个均匀分布随机数的均值和方差, (3)将这100个均匀分布的随机数,及其均值和方差保存到文本文件

  • C# Random类的正确应用方法

    Random类介绍 Random类一个用于产生 伪随机 数字的类.这里的伪随机表示有随机性但是可以基于算法模拟出随机规律. Random类的构造方式有两种. Random r= new Random().会以当前系统时间作为默认种子构建一个随机序列 Random r = new Random(unchecked((int)DateTime.Now.Ticks));.自定义一个种子,通常会使用时间Ticks. 随机性保证 由于Random的 伪随机 性,所以如果多个Random随机序列生成的时间间

  • JAVA的Random类的用法详解

    Random类 (java.util) Random类中实现的随机算法是伪随机,也就是有规则的随机.在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字. 相同种子数的Random对象,相同次数生成的随机数字是完全相同的.也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同.这点在生成多个随机数字时需要特别注意. 下面介绍一下Random类的使用,以及如何生成指定区间的随机数组以及实现程

  • 深入浅析Random类在高并发下的缺陷及JUC对其的优化

    Random可以说是每个开发都知道,而且都用的很6的类,如果你说,你没有用过Random,也不知道Random是什么鬼,那么你也不会来到这个技术类型的社区,也看不到我的博客了.但并不是每个人都知道Random的原理,知道Random在高并发下的缺陷的人应该更少.这篇,我就来分析下Random类在并发下的缺陷以及JUC对其的优化. Random的原理及缺陷 public static void main(String[] args) { Random random = new Random();

  • 关于Java错误提示之找不到或无法加载主类的问题及正确处理方法

    目录 前言 javac xxx.java 编译需要相对物理路径 java xxx 执行需要虚拟路径 总结 前言 一般情况下,我们都使用工具进行代码的编辑和调试,例如eclipse .Manven.Android Studio.sublime.vim.notepad.记事本等.当我们用eclipse android studio等创建的project和java类文件,都是有包名的,用工具正常编译和运行,但此时切换到命令行执行的时候:javac xxx.javajava xxx的时候 极有可能出现找

  • 在JavaScript中调用Java类和接口的方法

    前言 本文中所有的代码使用 JavaScript 编写,但你也可以用其他兼容 JSR 223 的脚本语言.这些例子可作为脚本文件也可以在交互式 Shell 中一次运行一个语句的方式来运行.在 JavaScript 中访问对象的属性和方法的语法与 Java 语言相同. 本文包含如下几部分: 1.访问 Java 类 为了在 JavaScript 中访问原生类型或者引用 Java 类型,可以调用 Java.type() 函数,该函数根据传入的完整类名返回对应对象的类型.下面代码显示如何获取不同的对象类

  • 跟我学习javascript创建对象(类)的8种方法

    8中javascript创建对象(类)的方法,依依介绍给大家,希望大家喜欢. 1. 使用Object构造函数来创建一个对象 下面代码创建了一个person对象,并用两种方式打印出了Name的属性值. var person = new Object(); person.name="kevin"; person.age=31; alert(person.name); alert(person["name"]) 上述写法的另外一种表现形式是使用对象字面量创建一个对象,不要

  • java同步锁的正确使用方法(必看篇)

    同步锁分类 对象锁(this) 类锁(类的字节码文件对象即类名.class) 字符串锁(比较特别) 应用场景 在多线程下对共享资源的安全操作. 需求:启动5个线程对共享资源total进行安全操作. 同步锁在多线程单例模式下的使用 以上三类同步锁都可以. package cn.myThread; public class MyThread implements Runnable { private static int total = 10; @Override public void run()

  • Android 开发之Dialog中隐藏键盘的正确使用方法

    Android 开发之Dialog中隐藏键盘的正确使用方法 场景:弹出一个Dialog,里面有一个EditText,用来输入内容,因为输入时,需要弹出键盘,所以当Dialog消失时,键盘要一起隐藏. 现在我们做一个自定义的Dialog MyDialog extends Dialog 一开始认为这个功能很容易实现,于是写了下面的代码 //Dialog的构造函数中写 this.setOnDismissListener(new OnDismissListener() { @Override publi

  • Eclipse下Javassist正确使用方法代码解析

    这两天看到Hibernate的代理部分,第一反应是底层使用了反射,针对用户实体生成了代理类,后来反应过来了,反射没有任何可以产生新类的能力,也就顺理成章地找到了Javassist(下载地址). 在网上搜索到的大部分教程,都是针对Javassist的API进行一番讲解,但是最后,往往没有一个加载过程,而笔者模仿这些教程进行类的加载时,加载到的结果都是原来的类,并没有产生字节码被修改的内容. 在经过一番探索后,笔者发现,网上的大部分教程中的最后一步,保存字节码,使用的均是writeFile的无参数重

  • 解读@RequestBody的正确使用方法

    本文主要研究的是关于@RequestBody的正确使用方法的相关内容,具体如下. 最近在接收一个要离职同事的工作,接手的项目是用SpringBoot搭建的,其中看到了这样的写法: @RequestMapping("doThis") public String doThis(HttpServletRequest request, @RequestParam("id") Long id, // 用户ID @RequestParam("back_url"

随机推荐