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