前言对于随机数我们应该不陌生。在业务中,我们用它来生成验证码,或者对重复性要求不高的id,甚至在年会上用它来抽奖。今天就来讨论一下这个问题。如果使用不当,会引起一系列的问题。Java中的随机数我们需要在Java中随机生成一个数字。在java开发中,我们通常使用java.util.Random,它提供了一种伪随机生成机制。Jvm通过传入的种子(seed)来决定生成随机数的间隔。只要种子相同,得到的随机数序列就是一致的。结果是可以预见的。是伪随机数的实现,不是真正的随机数。确定使用什么但是有些用例直接使用可能会出现一些意想不到的问题。Random的一个常见用法://Random实例Randomrandom=newRandom();//调用nextInt()方法除了nextDouble(),nextBoolean(),nextFloat(),...random.nextInt();或者,我们可以使用java中的数学计算类:Math.random();Math类只包含一个Random实例来生成随机数:publicstaticdoublerandom(){Randomrnd=randomNumberGenerator;if(rnd==null){//ReturnanewRandomExamplernd=initRNG();}returnrnd.nextDouble();}java.util.Random的使用是线程安全的。但是,在不同线程上并发使用同一个Random实例可能会导致争用,从而导致性能下降。这样做的原因是使用所谓的种子来生成随机数。种子是一个简单的数字,它为生成新的随机数提供了基础。看一下Random中的next(intbits)方法:while(!seed.compareAndSet(oldseed,nextseed));返回(整数)(下一个种子>>>(48位));首先,旧种子和新种子存储在两个辅助变量中。在这一点上,创造新种子的原则并不重要。要保存新种子,请使用compareAndSet()方法将旧种子替换为下一个新种子,但这只会在旧种子对应于当前设置的种子时触发。如果此时的值正在被并发线程操作,则该方法返回false,表示旧值与异常值不匹配。因为它是在循环内完成的,所以会发生自旋,直到变量与异常值匹配为止。这会导致性能不佳和线程争用。多线程下的随机数如果有更多的线程主动生成新的随机数与同一个Random实例,则出现上述情况的概率更高。对于生成很多(非常多)随机数的程序,不建议这样做。在这种情况下,您应该使用Java1.7版本中添加的ThreadLocalRandom。ThreadLocalRandom扩展Random并添加选项以将其使用限制在相应的线程实例中。为此,ThreadLocalRandom的实例保存在相应线程的内部映射中,并通过调用current()返回相应的Random。使用方法如下:ThreadLocalRandom.current().nextInt()安全随机数通过对Random的一些分析,我们可以知道Random其实是伪随机,规律可推,依赖于种子.如果我们搞抽奖或者其他对随机数比较敏感的场景,就不适合用Random了,很容易被利用。JDK提供了SecureRandom来解决这个问题。SecureRandom是一个强随机数生成器,可以生成高强度的随机数。高强度随机数的产生取决于两个重要因素:种子和算法。算法可以有很多种,通常如何选择种子是一个非常关键的因素。Random的种子是System.currentTimeMillis(),所以它的随机数是可预测的弱伪随机数。产生强伪随机数的思路:收集电脑的各种信息,键盘输入时间,内存使用状态,硬盘空闲空间,IO延迟,进程数,线程数等信息,CPU时钟,来得到一个近似随机的种子,主要是为了实现不可预测性。通俗点说,就是用一种加密算法生成一个很长的随机种子,这样你就猜不到种子,也推不出随机序号。小结今天我们讨论了业务中经常使用的随机数的一些机制以及在某些场景下的一些陷阱。希望大家在使用随机数的时候能够避免这样的陷阱。
