Java中生成随机数Random、ThreadLocalRandom、SecureRandom、Math.random()

转载自:https://blog.csdn.net/lovesomnus/article/details/8257849

我们来说说Java常见的生成随机数的几种方式:Random,ThreadLocalRandom,SecureRandom;其实产生随机数有很多种方式但我们常见的就这几种,如果需要详细了解这个三个类,可以查看JAVA API.



Random random = new Random();  
int a = random.nextInt(5);//随机生成0~4中间的数字 

其实Random是有构造函数的,他的参数可以传一个long类型的值,当使用空的构造的时候,使用的实际上是System.nanoTime()也就是当前时间毫秒数的值,我们把这个叫做 种子 。

种子是干什么的呢,实际上我们生成的随机数都是伪随机数,而想要使我们生成的随机数强度更高,就需要更好的 算法 和种子。一般情况下,要使用Random去生成随机数,直接用空构造函数就可以了。那么这个种子到底有什么用呢,实际上读者去试验一下就知道了,我们使用固定随机数,比如1,然后我们连续次去new这个Random,然后去生成一个随机数,像下面这样,你会发现,三个数的结果是一样的。

    /**
     * -1157793070
     * 1913984760
     * 1107254586
     */
    @Test
    public void test2(){
        Random random = new Random(10); 
        for (int i = 0; i < 3; i++){
            System.out.println(random.nextInt());
        }
    }

所以我们一定不能把这个种子写死,用当前时间毫秒数,还是比较好些。另外,使用Random尽量不要重复new对象,其实也没什么意义的。最后说一点,Random是线程安全的,去 这里 的官方文档可以看到,“Instances of java.util.Random are threadsafe.”。但是在 多线程的表现中,他的性能很差。

在Java的API帮助文档中,总结了一下对这个Random的描述:

  1. java.util.Random类中实现的随机算法是伪随机,也就是有规则的随机,所谓有规则的就是在给定种(seed)的区间内随机生成数字;
  2. 相同种子数的Random对象,相同次数生成的随机数字是完全相同的;

  3. Random类中各方法生成的随机数字都是均匀分布的,也就是说区间内部的数字生成的几率均等;




这个类是Java7新增的类,给多线程并发生成随机数使用的。为什么ThreadLocalRandom要比Random快呢,这是因为Random在生成随机数的时候使用了CAS(compare and set),但是ThreadLocalRandom却没有使用。

下面是java.util.Random的生成随机数的方法:

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));
}

而这边的seed是一个全局变量:

/**
 * The internal state associated with this pseudorandom number generator.
 * (The specs for the methods in this class describe the ongoing
 * computation of this value.)
 */
private final AtomicLong seed;

多个线程同时获取随机数的时候,会竞争同一个seed,导致了效率的降低。

可见,其中通过CAS方式保证其线程安全性。这在高并发的环境中由于线程间的竞争必然带来一定的性能损耗。

ThreadLocal此时就派上用场了,ThreadLocalRandom是通过ThreadLocal改进的用于随机数生成的工具类,每个线程单独持有一个ThreadLocalRandom对象引用,这就完全杜绝了线程间的竞争问题。

另外ThreadLocalRandom的实例化比较特别,下面简单举例一下。

ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();  
int a  = threadLocalRandom.nextInt(5);  
由于是和线程绑定的,所以他也是从当前线程获取的。



在需要频繁生成随机数,或者安全要求较高的时候,不要使用Random,这个很好理解吧,从我们最开始的介绍中可以知道,Random生成的值其实是可以预测的。

内置两种随机数算法,NativePRNGSHA1PRNG,看实例化的方法了。通过new来初始化,默认来说会使用NativePRNG算法生成随机数,但是也可以配置-Djava.security参数来修改调用的算法。如果是/dev/[u]random两者之一就是NativePRNG,否则就是SHA1PRNG

在 jvm 启动参数这样加就好了,-Djava.security=file:/dev/urandom

当然还可以通过getInstance来初始化对象,有一个参数的,直接传一个算法名就行,如果不存在算法抛异常;另外有两个参数的,第二个参数还可以指定算法程序包。下面来看下实现代码。

SecureRandom secureRandom = new SecureRandom();
SecureRandom secureRandom3 = SecureRandom.getInstance("SHA1PRNG");
SecureRandom secureRandom2 = SecureRandom.getInstance("SHA1PRNG", "SUN");

当然我们使用这个类去生成随机数的时候,一样只需要生成一个实例每次去生成随机数就好了,也没必要每次都重新生成对象。另外,这个类生成随机数,首次调用性能比较差,如果条件允许最好服务启动后先调用一下nextInt()

另外,实际上SHA1PRNG的性能将近要比NativePRNG的性能好一倍,synchronized的代码少了一半,所以没有特别重的安

全需要,尽量使用SHA1PRNG算法生成随机数。



这也是个比较常用的生成随机数的方式,默认生成0~1之间的小数.

总结:

1、单机中如果对安全性要求不高的情况下,使用 Random;对安全性要求高,就用 SecureRandom;

SecureRandom里有两种算法,SHA1PRNG 和 NativePRNG,SHA1PRNG的性能好,但是NativePRNG的安全性高。

2、Random 是线程安全的,用CAS来保持,但是性能比不高,所以多线程中,尽量使用 java并发包里的 ThreadLocalRandom

避免了线程之间的竞争导致的性能问题

使用Random生成随机数

已标记关键词 清除标记
众所周知,随机数是任何一种编程语言最基本的特征之一。而生成随机数的基本方式也是相同的:产生一个0到1之间的随机数。看似简单,但有时我们也会忽略了一些有趣的功能。 我们从书本上学到什么? 最明显的,也是直观的方式,在Java生成随机数只要简单的调用: 1.java.lang.Math.random() 在所有其他语言生成随机数就像是使用Math工具类,如abs, pow, floor, sqrt和其他数学函数。大多数人通过书籍、教程和课程来了解这个类。一个简单的例子:从0.0到1.0之间可以生成一个双精度浮点数。那么通过上面的信息,开发人员要产生0.0和10.0之间的双精度浮点数会这样来写: 1.Math.random() * 10 而产生0和10之间的整数,则会写成: 1.Math.round(Math.random() * 10) 进阶 通过阅读Math.random()的源码,或者干脆利用IDE的自动完成功能,开发人员可以很容易发现,java.lang.Math.random()使用一个内部的随机生成对象 - 一个很强大的对象可以灵活的随机产生:布尔值、所有数字类型,甚至是高斯分布。例如: 1.new java.util.Random().nextInt(10) 它有一个缺点,就是它是一个对象。它的方法必须是通过一个实例来调用,这意味着必须先调用它的构造函数。如果在内存充足的情况下,像上面的表达式是可以接受的;但内存不足时,就会带来问题。 一个简单的解决方案,可以避免每次需要生成一个随机数时创建一个新实例,那就是使用一个静态类。猜你可能想到了java.lang.Math,很好,我们就是改良java.lang.Math的初始化。虽然这个工程量低,但你也要做一些简单的单元测试来确保其不会出错。 假设程序需要生成一个随机数来存储,问题就又来了。比如有时需要操作或保护种子(seed),一个内部数用来存储状态和计算下一个随机数。在这些特殊情况下,共用随机生成对象是不合适的。 并发 在Java EE多线程应用程序的环境,随机生成实例对象仍然可以被存储在类或其他实现类,作为一个静态属性。幸运的是,java.util.Random是线程安全的,所以不存在多个线程调用会破坏种子(seed)的风险。 另一个值得考虑的是多线程java.lang.ThreadLocal的实例。偷懒的做法是通过Java本身API实现单一实例,当然你也可以确保每一个线程都有自己的一个实例对象。 虽然Java没有提供一个很好的方法来管理java.util.Random的单一实例。但是,期待已久的Java 7提供了一种新的方式来产生随机数: 1.java.util.concurrent.ThreadLocalRandom.current().nextInt(10) 这个新的API综合了其他两种方法的优点:单一实例/静态访问,就像Math.random()一样灵活。ThreadLocalRandom也比其他任何处理高并发的方法要更快。 经验 Chris Marasti-Georg 指出: 1.Math.round(Math.random() * 10) 使分布不平衡,例如:0.0 - 0.499999将四舍五入为0,而0.5至1.499999将四舍五入为1。那么如何使用旧式语法来实现正确的均衡分布,如下: 1.Math.floor(Math.random() * 11) 幸运的是,如果我们使用java.util.Randomjava.util.concurrent.ThreadLocalRandom就不用担心上述问题了。 Java实战项目里面介绍了一些不正确使用java.util.Random API的危害。这个教训告诉我们不要使用: 1.Math.abs(rnd.nextInt())%n 而使用: 1.rnd.nextInt(n)
相关推荐
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页