随机数咱们应该不陌生,业务中咱们用它来生成验证码,或者对重复性要求不高的id,甚至咱们还用它在年会上搞抽奖。今天咱们来探讨一下这个东西。若是使用不当会引起一系列问题。java
咱们须要在Java中随机生成一个数字。java开发中咱们一般使用java.util.Random
来搞,它提供了一种伪随机的生成机制。Jvm 经过传入的种子(seed)来肯定生成随机数的区间,只要种子同样,获取的随机数的序列就是一致的。并且生成的结果都是能够预测的。是一种伪随机数的实现,而不是真正的随机数。来肯定使用的可是有些用例直接使用可能会致使一些意想不到的问题。Random
的一个广泛用法:算法
// Random 实例
Random random = new Random();
//调用 nextInt() 方法 此外还有nextDouble(), nextBoolean(), nextFloat(), ...
random.nextInt();复制代码
或者,咱们可使用java中的数学计算类:安全
Math.random();复制代码
Math类只包含一个Random实例来生成随机数:多线程
public static double random() {
Random rnd = randomNumberGenerator;
if (rnd == null) {
// 返回一个新的Random实例
rnd = initRNG();
}
return rnd.nextDouble();
}复制代码
java.util.Random
的用法是线程安全的。可是,在不一样线程上并发使用相同的Random
实例可能会致使争用,从而致使性能不佳。其缘由是使用所谓的种子来生成随机数。种子是一个简单的数字,它为生成新的随机数提供了基础。咱们来看看Random
中的next(int bits)
方法:并发
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = seed.get();
nextseed = (oldseed * multiplier addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));}复制代码
首先,旧种子和新种子存储在两个辅助变量上。在这一点上,创造新种子的原则并不重要。要保存新种子,使用compareAndSet()
方法将旧种子替换为下一个新种子,但这仅仅在旧种子对应于当前设置的种子的条件下才会触发。若是此时的值由并发线程操纵,则该方法返回false
,这意味着旧值与例外值不匹配。由于是循环内进行的操做,那么会发生自旋,直到变量与例外值匹配。这可能会致使性能不佳和线程竞争。dom
若是更多线程主动生成具备相同Random的实例的新随机数,则上述状况发生的几率越高。对于生成许多(很是多)随机数的程序,不建议使用这种方式。在这种状况下,您应该使用ThreadLocalRandom
,它在1.7版本中添加到Java中。ThreadLocalRandom
扩展了Random
并添加选项以限制其使用到相应的线程实例。为此,ThreadLocalRandom
的实例保存在相应线程的内部映射中,并经过调用current()
来返回对应的Random
。使用方式以下:性能
ThreadLocalRandom.current().nextInt()复制代码
经过对Random
的一些分析咱们能够知道Random
事实上是伪随机,是能够推导出规律的,并且依赖种子(seed)。若是咱们搞抽奖或者其余一些对随机数敏感的场景时,用Random
就不合适了,容易被人钻空子。JDK提供了SecureRandom
来解决这个事情。SecureRandom
是强随机数生成器,它能够产生高强度的随机数,产生高强度的随机数依赖两个重要的因素:种子和算法。算法是能够有不少的,一般如何选择种子是很是关键的因素。 Random的种子是System.currentTimeMillis()
,因此它的随机数都是可预测的, 是弱伪随机数。强伪随机数的生成思路:收集计算机的各类信息,键盘输入时间,内存使用状态,硬盘空闲空间,IO延时,进程数量,线程数量等信息,CPU时钟,来获得一个近似随机的种子,主要是达到不可预测性。说的更通俗就是,使用加密算法生成很长的一个随机种子,让你没法猜想出种子,也就没法推导出随机序列数。this
今天咱们探讨了业务中常常使用的随机数的一些机制和一些场景下的一些陷阱,但愿你在使用随机数的时候能避免这种陷阱。加密
关注公众号:码农小胖哥,获取更多资讯
spa