详解JAVA的两种产生随机值的方法

1.Math类的random()方法(静态)

JAVA中支持产生随机值的最基本的方法就是调用Math类的random()方法,
产生一个[0,1)之间的数,类型为double。
random()方法源代码如下:

//创建一个Random类型的静态变量randomNumberGenerator
private static Random randomNumberGenerator;

//random()代码块
public static double random(){
Random rnd=randomNumberGenerator;//将静态变量对Random对象rnd赋值
if(rnd==null) rnd=initRNG();
return rnd.nextDouble();
}

//initRNG()代码块
private static synchronized Random initRNG(){
Random rnd=randomNumberGenerator;

/*三目运算符:rnd为空,即静态变量为空,若静态变量为空,
则创建一个Random对象,并将该静态变量作为其引用变量并返回该对象;
若静态变量不为空,则返回rnd。
*/
return (rnd==null)?(randomNumberGenerator=new Random()):rnd
}

由此可见,Math.random()这种取随机值的方法,根源上还是调用了Random类的方法,只是若需要取0~1之间double型的随机值,直接调用Math.random()更为便捷。

保留疑问:
1.initRNG()部分。有个三目运算符,先要判断rnd是否为空。在这之前,已经把静态变量赋值给rnd了,并且静态变量是创建时就有默认值,所以rnd什么时候会为空
2. Math里的这个random()方法 只是调用了Random类里的nextDouble()方法 那它创建randomNumberGenerator这个静态变量的意义在哪里

插句题外话:关于Math.random()源代码中的synchronized关键字
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 
 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
 2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
 3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
 4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

Math.random()的使用:

public class Math_random_exercise {
public static void main(String args[]){
    for(int i=0;i<3;i++)
        System.out.println(Math.random());
  }  }

运行结果:

0.17435065906178981
0.9094104328358958
0.22283486987084544

2.Random类

我们先来了解一下Random类中的一些随机生成值方法:

public int nextInt()
public long nextLong()
public boolean nextBoolean()
public float nextFloat()
public double nextDouble()
public void nextBytes(byte[] bytes)//产生随机字节,字节个数为bytes长度

事实上,Random产生的随机数并不是真正的随机数,而一般称为伪随机数。伪随机数一般都是基于一个种子数的。每需要一个随机数,就对种子数进行一些数学运算,从而得到一个伪随机数序列。当种子数相同时,得出的伪随机数序列是相同的。相反,种子数不同,伪随机数序列就不同。

在Random类中,有两个构造方法。

public Random()
public Random(long seed)

第二种含参不必多说,调用者传递种子数。
而在第一种默认构造方法中,无法传递种子数,它会自动生成一个种子数,这里的种子数才是真正的随机数。下面我们来看一下相关源代码:

private static final AtomicLong seedUniquifier
            =new AtomicLong(8682522807148012L);

//Random类无参构造方法代码块
 public Random(){
 /*
 1."^"符号为按位相或
 2.seedUniquifier为多线程相关知识,我们择日再研究
 3.System.nanoTime()为返回一个更高精度(纳秒)的当前时间
 */
 this(seedUniquifier())^System.nanoTime());
 }

//seedUniquifier()代码块
   public static long seedUniquifier(){
    for(;;){
    long current=seenUniquifier.get();
    long next=current*181783497276652981L;
    if(seedUniquifier.compareAndSet(current,next))
    return next;
  }
}

当有了随机的种子数之后,我们再来看一个next(int bits)方法的源码

   private static final long multiplier=0x5DEECE66DL;
   private static final long addend=0xBL;
   private static final long mask=(1L<<48)-1;
   //next代码块
   protected int next(int bits){
   long oldseed,nextseed;
   AtomicLong seed=this.seed;
      do{ 
       oldseed=seed.get();
       extseed=(oldseed*multiplier+addend)&mask;
         }while(!seed.compareAndSet(oldseed,nextseed));
    return (int)(nextseed>>>(48-bits));
}

其中重点的一句:
extseed=(oldseed*multiplier+addend)&mask;

旧的种子(oldseed)乘一个数(multiplier),再加上一个数(addend),然后取低48位作为结果(mask相与)。这个方法叫做线性同余随机数生成器。
将种子数生成与next()相结合,就有了这些方法:

public int nextInt(){
    return next(32);
}
public long nextLong(){ 
    return ((long)(next(32))<<32)+next(32);
}
public float nextFloat(){
    return next(24)/((float)(1<<24));
}
public boolean nextBoolean(){
    return next(1)!=0;
}

说起来,学完Random,发现自己的二进制运算还有多线程方面的知识还是差得远。立flag了,今天好好补一补。

猜你喜欢

转载自blog.csdn.net/callmeletty/article/details/80463450