Java中使用随机数的正确姿势是什么? java随机数Random的使用

随机数我们应该不陌生 , 业务中我们用它来生成验证码 , 或者对重复性要求不高的id , 甚至我们还用它在年会上搞抽奖 。今天我们来探讨一下这个东西 。如果使用不当会引发一系列问题 。
java中的随机数我们需要在Java中随机生成一个数字 。java开发中我们通常使用java.util.Random来搞 , 它提供了一种伪随机的生成机制 。Jvm 通过传入的种子(seed)来确定生成随机数的区间 , 只要种子一样 , 获取的随机数的序列就是一致的 。而且生成的结果都是可以预测的 。是一种伪随机数的实现 , 而不是真正的随机数 。来确定使用的但是有些用例直接使用可能会导致一些意想不到的问题 。Random的一个普遍用法:
【Java中使用随机数的正确姿势是什么? java随机数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 , 这意味着旧值与例外值不匹配 。因为是循环内进行的操作 , 那么会发生自旋 , 直到变量与例外值匹配 。这可能会导致性能不佳和线程竞争 。
多线程下的随机数如果更多线程主动生成具有相同Random的实例的新随机数 , 则上述情况发生的概率越高 。对于生成许多(非常多)随机数的程序 , 不建议使用这种方式 。在这种情况下 , 您应该使用ThreadLocalRandom , 它在1.7版本中添加到Java中 。ThreadLocalRandom扩展了Random并添加选项以限制其使用到相应的线程实例 。为此 , ThreadLocalRandom的实例保存在相应线程的内部映射中 , 并通过调用current()来返回对应的Random 。使用方式如下:
ThreadLocalRandom.current().nextInt()安全的随机数通过对Random的一些分析我们可以知道Random事实上是伪随机 , 是可以推导出规律的 , 而且依赖种子(seed) 。如果我们搞抽奖或者其他一些对随机数敏感的场景时 , 用Random就不合适了 , 容易被人钻空子 。JDK提供了SecureRandom来解决这个事情 。

推荐阅读