代码评审——随机数Random问题

问题描述:

为了获取唯一值,经常会依赖产生随机数来保证唯一性。在获取随机数时,如果使用错误的方法,会比较低效。

可以参考以下代码:

public static String geneRundomNo(){
    Random r=new Random();
    int num=r.nextInt(100000);
    return "" + num;
}

此时,使用静态代码扫描工具,会出现以下提示:
Save and re-use this “Random”.
在这里插入图片描述


原因分析:

可以看下来自静态代码扫描工具的说明:

Creating a new Random object each time a random value is needed is inefficient and may produce numbers which are not random depending on the JDK. For better efficiency and randomness, create a single Random, then store, and reuse it.
The Random() constructor tries to set the seed with a distinct value every time. However there is no guarantee that the seed will be random or even uniformly distributed. Some JDK will use the current time as seed, which makes the generated numbers not random at all.
This rule finds cases where a new Random is created each time a method is invoked and assigned to a local random variable.

简单的说就是每次需要一个随机值时创建一个新的Random对象是低效的,并且可能会根据JDK的版本不同产生非随机的数字。


解决方案:

首先看下静态代码扫描工具给出的建议。

private Random rand = SecureRandom.getInstanceStrong();  // SecureRandom is preferred to Random

public void doSomethingCommon() {
  int rValue = this.rand.nextInt();
  //...

如果按照这个建议执行,那么会产生2个问题

1、按照上述方式引入,还需要解决构建函数的问题。

当引入java.security.SecureRandom后,需要解决构造函数问题。
可以通过以下方式解决:

private static Random rand;
static {
    try {
        rand = SecureRandom.getInstanceStrong();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
}

2、使用SecureRandom.getInstanceStrong()会导致线程阻塞问题,这个问题也是最严重的。

原因:不同的系统环境执行的底层代码不相同,在linux系统中是通过底层NativePRNG方法,通过/dev/random方式读取随机数,/dev/random方式受系统环境的影响容易造成线程阻塞,在windows系统中通过generateSeed的native方法读取,不会阻塞线程。具体的分析过程可以参考《SecureRandom.getInstanceStrong()引发的线程阻塞问题分析》。这篇文章写的非常详细了。

解决方案:使用new SecureRandom()替换SecureRandom.getInstanceStrong()。
请注意:这种解决方案是伪随机。

其他相关知识点:

以下内容摘自文章《SecureRandom 引发的线程阻塞》

  1. SecureRandom本身并不是伪随机算法的实现,而是使用了其他类提供的算法来获取伪随机数。

  2. 如果简单的new一个SecureRandom对象的话,在不同的操作平台会获取到不同的算法,windows默认是SHA1PRNG,Linux的话是NativePRNG。

  3. Linux下的NativePRNG,如果调用generateSeed()方法,这个方法会读取Linux系统的/dev/random文件,这个文件在JAVA_HOME/jre/lib/securiy/java.security里面有默认定义。而/dev/random文件是动态生成的,如果没有数据,就会阻塞。也就造成了第一个现象。

  4. 可以使用-Djava.security.egd=file:/dev/./urandom (这个文件名多个u)强制使用/dev/urandom这个文件,避免阻塞现象。中间加个点的解释是因为某个JDK BUG,SO那个帖子有链接。

  5. 如果使用SecureRandom.getInstanceStrong()这种方法初始化SecureRandom对象的话,会使用NativePRNGBlocking这个算法,而NativePRNGBlocking算法的特性如下:
    NativePRNGBlocking uses /dev/random for all of the following operations:
    Initial seeding: This initializes an internal SHA1PRNG instance using 20 bytes from /dev/random
    Calls to nextBytes(), nextInt(), etc.: This provides the XOR of the output from the internal SHA1PRNG instance (see above) and data read from /dev/random
    Calls to getSeed(): This provides data read from /dev/random
    可见这个算法完全依赖/dev/random,所以当这个文件随机数不够的时候,自然会导致卡顿了。

  6. 如果使用NativePRNGBlocking算法的话,4中的系统参数失效!!!

  7. 一般使用SecureRandom不需要设置Seed,不需要设置算法,使用默认的,甚至一个静态的即可,如果有需求的话可以在运行一段时间后setSeed一下

参考文章:《一文详解安全随机数》这篇文章详细介绍了如何使用安全随机数。

如果这篇博客对大家有所帮助,我希望能得到各位的免费点赞收藏,作为对我的鼓励和支持。
同时,也请大家在评论区留下您宝贵的意见和建议,我将非常欢迎。
感谢大家的支持评论收藏!!!

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是老师做过的一个项目,抽取了当中核心之一的内容给大家试试,有些难度。 1、实验目的 1)掌握C#命令行参数的接收; 2)掌握C#中的泛型用法; 3)掌握C#的基本流程语句; 4)掌握C#的随机数生成; 5)了解C#的异常处理流程; 6)掌握C#的计时器及对代码的性能优化。 7)本实验注重算法实现,在实现功能的前提下,尽量优化计算速度。 2、实验要求 这是一个实际的项目衍生出来的核心算法之一。防伪码是我们现在经常在商品上看到的防伪手段之一,现在需要编写一个防伪码生成器,按照输入参数生成防伪码,并且把生成的时间及指定的防伪码输出。 1)防伪码的组成 防伪码由以下字符组成:0123456789ABCDEFGHJKLMNPQRSTUVWXYZ (数字1和字母I相近、数字0和字母O相近,所以去掉字母I和字母O。全部字母大写) \ 2)在命令行中输入2个参数,分别是: 防伪码长度 防伪码个数 例如:在命令行中调用程序为:学号.exe 10 10000 指的是防伪码长度为10,生成10000个防伪码。 3)防伪码的生成及注意事项 防伪码的长度由命令行参数决定; 所生成的防伪码不能重复(按照以上例子,生成了10000个防伪码,这10000个防伪码就肯定不能重复)。 3、提交内容 1)请把整个项目源代码压缩为RAR文件进行提交 2)运行: a) exe 10 10000 b) exe 20 1000000 c) exe 50 1000000 (即防伪码长度为10,10000个;长度为20,一百万个;长度为50,一百万个)共三个测试用例,记下时间,贴在“自评备注”中,格式如下: a) 时间;b) 时间;c) 时间 下面可以写写自己的感想等 4、实验评分 程序能运行,按照全部要求实现:A及以上 程序能运行,但有Bug的(指生成重复的防伪码等):B 程序不能运行,但有思路的:C及以下 0:发现抄袭行为,本次实验0分,累计两次,平时成绩0分。 额外要求:代码命名必须有一定规范、代码格式必须靓仔,歪歪扭扭的扣分。 5、采用的函数及思路 1)开发时,在解决方案中,鼠标右键选择项目,然后点选“属性”,在“调试”项目中的“命令行参数”里面,可以预先输入需在命令行接收的参数,这样开发的时候就可以直接读入了。注意,不用对这些参数进行校验,默认输入的都是正确的参数,注意程序运行时的异常处理。 2)程序运行计时 在最上面加入using System.Diagnostics; 在程序头加入: Stopwatch timer1 = new Stopwatch();//计时器类 timer1.Start();//开始计时 在程序最尾加入: timer1.Stop();//停止计时 double dMilliseconds = timer1.Elapsed.TotalMilliseconds; Console.WriteLine("生成个数为:{0},运行时间为:{1}", icount, dMillisecondes); Console.ReadKey(); Stopwatch是C#一个类似秒表的东西,用来计算程序的运行时间,注意,必须按照要求,计时器在程序运行时就要开启,在结束时停止并输出结果,不能放置在其他地方。 3)防伪码生成思路 例如,有以下定义: string strTableChar = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ"; 生成一个从0到strTableChar.Length的数字a,然后使用strTableChar[a]就可以随机返回一个字母,重复n次(n等于防伪码的长度),这样就可以组合到一串随机字符串,也就是防伪码了。 6、思路及技巧 1)随机数生成:种子的选择问题,可用默认的、GUID、RNGCryptoServiceProvider等等作为随机数种子…… 2)怎么样保证,新生成的防伪码和以生成的防伪码有没有重复? 3)stringBuilder的用法,试试用string和stringBuilder有什么不同?听听老师的课…… 4)多次运行程序后,会不会发现程序运行速度会加快?看看这里:《告诉一个不一样的.NET Framework字符串驻留》,另外也可以找找对应.NET Framework底层实现的文章。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值