Math源码java_浅析Java源码之Math.random()
从零⾃学java消遣⼀下,看书有点脑阔疼,不如看看源码!(╹◡╹) """
J S中Math调⽤的都是本地⽅法,底层全是⽤C++写的,所以完全⽆法观察实现过程,Java的⼯具包虽然也有C/C++的介⼊,不过也有些是⾃⼰实现的。
本篇⽂章主要简单阐述Math.random()的实现过程。
M ath⾪属于java.lang包中,默认加载。本⾝是⼀个final类,⽅法都是静态⽅法,所以使⽤的时候不需要⽣成⼀个实例,直接调⽤Math.XX 就⾏了。
⼀步⼀步观察该⽅法,⾸先是java.lang.Math:
public final class Math {
// ⼤量静态变量与⽅法
// ...
private static Random randomNumberGenerator;
private static synchronized void initRNG() {
if (randomNumberGenerator == null)
randomNumberGenerator = new Random();
}
public static double random() {
if (randomNumberGenerator == null) initRNG();
Double();
}
// ...other
}
这⾥⾯与random相关的操作有3个:
1、声明⼀个私有静态Random类randomNumberGenerator
2、若randomNumberGenerator未初始化,调⽤new Random()将其初始化
3、若randomNumberGenerator已经初始化,调⽤nextDouble⽅法并将其值返回
tips:synchronized关键字代表同步执⾏此⽅法,Java为多线程,所以为了保证randomNumberGenerator对象只被初始化⼀次,需要该关键字。⽐如两个线程同时调⽤了Math.random(),线程A发现rXX未被初始化,进⼊initRNG调⽤new Random()⽅法。此时线程B也发现了rXX未被初始化,但是initRNG是同步⽅法,所以挂起等待线程A执⾏完毕。当线程A执⾏完后把rXX初始化了,所以在initRNG中的if 判断,线程B会直接返回。
所以简单来讲,random⽅法会在第⼀次调⽤时⽣成⼀个randomNumberGenerator对象,并调⽤其nextDouble⽅法⽣成随机数,之后的调⽤就只要持续调⽤此⽅法返回随机数就⾏了。
下⾯来看Random类是个什么⿁,来源于java.util.Random:
public class Random implements java.io.Serializable {
// 静态变量
/** use serialVersionUID from JDK 1.1 for interoperability */
static final long serialVersionUID = 3905348978240129619L; private final AtomicLong seed;
private final static long multiplier = 0x5DEECE66DL;
private final static long addend = 0xBL;
private final static long mask = (1L << 48) - 1;
// constructor
public Random() { this(++seedUniquifier + System.nanoTime()); } private static volatile long seedUniquifier = 8682522807148012L; public Random(long seed) {
this.seed = new AtomicLong(0L);
setSeed(seed);
}
// 设置种⼦
synchronized public void setSeed(long seed) {
seed = (seed ^ multiplier) & mask;
this.seed.set(seed);
haveNextNextGaussian = false;
}
// 产⽣⼤数字
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = ();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seedpareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
// ⽣成随机数
public double nextDouble() {
return (((long)(next(26)) << 27) + next(27))
/ (double)(1L << 53);
}
// 其他不关⼼的⽅法
// nextBytes(bytes [])
/
/ nextInt
// nextInt(int)
// nextLong
// nextBoolean
// nextFloat
// Serializable相关
}
上述代码剔除了⼤量的注释,还有⼀些不需要关⼼的⽅法,本⽂只关注Math.random()调⽤相关⽅法。
对于这个类,⾸先来看看它的构造函数,理论上new⼀个Random实例是需要⼀个long类型的整数作为参数,但是代码⽤了this使其默认调⽤new Random(long)这个构造函数。⽽在构造函数中⼜⽣成了⼀个新类并赋值给实例变量seed,关于这个AtomicLong类其实没啥好讲的,简单看⼀下就⾏:
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
// valueOffset相关...
// 实例变量
private volatile long value;
// 构造函数
public AtomicLong(long initialValue) {
value = initialValue;
}
public AtomicLong() {}
// ⽅法
public final long get() {
return value;
}
public final void set(long newValue) {
value = newValue;
}
// 这个也会⽤到 但是不⽤关⼼具体实现
public final boolean compareAndSet(long expect, long update) {
return unsafepareAndSwapLong(this, valueOffset, expect, update);
}
// 其余不需要关⼼(其实我也看不懂)的⽅法
}
如果思想简单⼀点,可以看出这个类也很简单,初始化传参赋值,set设置,get获取,多简单!
现在回到Random类的构造函数中,实例变量被赋值,类的value为初始化的0(后缀L代表这是⼀个long类型整数)。下⼀步调⽤setSeed,传⼊构造函数的long类型seed变量(不是seed类),其值为:
++seedUniquifier + System.nanoTime()
// private static volatile long seedUniquifier = 8682522807148012L(8.6825e+15);
// 2^52 ~ 2^53
// 写⽂章时测试 => System.nanoTime() => 13230650355964(1.323e+13);
其中第⼀个变量为⼀个固定值,每次加1,另外⼀个为System.nanoTime(),该⽅法返回⼀个与当前时间相关的数字,具体我不关⼼。
两个相加后,作为初始种⼦出传⼊setSeed⽅法中,⽅法第⼀步会对seed进⾏⼆次计算:
seed = (seed ^ multiplier) & mask;
// private final static long multiplier = 0x5DEECE66DL;(25214903917 => 2.5214e+10)
// 2^34 ~ 2^35
// private final static long mask = (1L << 48) - 1;(2^48-1 => 1 => 2^48 = 2.8147+e14)
此处进⾏的是位运算,这⾥不⽤关⼼具体数值,只关注可能得到的最⼤最⼩值。
^ => 异或运算:3 ^ 4 => 011 ^ 100 = 111 => 7(不⼀样置1,否则置0)
可以看出,两个数字异或运算,假设其中较⼤的⼆进制位数为n,结果⼀定是⼩于等于2n-1,⽐如34,4为100三位,所以结果⼀定⼩于等于2^3-1,即7。
& => 与运算:3 & 4 => 011 & 100 = 000 => 0(都为1置1,否则置0)
可以看出,与运算的结果总是⼩于等于较⼩的那个数。
这样来再来看之前的位运算:
seed(2^52 ~ 2^53) ^ multiplier(2^34 ~ 2^35) => 0 ~ (2^53-1)
(seed ^ multiplier)(0 ~ 2^53-1) & mask(2^48-1) => 0 ~ 2^48-1
结论是种⼦的范围是在0 ~ 2^48-1之间。
测试代码:
public class test {
public static void main(String [] args){
pro b = new pro();
System.out.Value());
// 256403749474577
// 256458702577093
// 256431328421593
}
}
class pro{
long seed = 8682522807148012L + System.nanoTime();
long multiplier = 0x5DEECE66DL;
long mask = (1L << 48) - 1;
long getValue(){
return (seed ^ multiplier) & mask;
}
}
构造函数调⽤完后,现在来看nextDouble,这个⽅法除去位运算,本质上就是调⽤了两次next⽅法:
public double nextDouble() {
return (((long)(next(26)) << 27) + next(27))
/ (double)(1L << 53);
}
所以直接看next⽅法:
protected int next(int bits) {
long oldseed, nextseed;
AtomicLong seed = this.seed;
do {
oldseed = ();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seedpareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}
java生成随机数的方法⽅法内部声明了2个long类型种⼦:oldseed、nextseed,通过get⽅法取得之前位运算得到的seed赋值给oldseed,然后再次通过运算得到⼀个nextseed的值,并传给seedpareAndSet(oldseed, nextseed)⽅法中。
关于这个⽅法,源码⾥是这样的:
// urrent.atomic.AtomicLong;
public class AtomicLong extends Number implements java.io.Serializable {
public final boolean compareAndSet(long expect, long update) {
return unsafepareAndSwapLong(this, valueOffset, expect, update);
}
}
// sun.misc.Unsafe.java
public native boolean compareAndSwapLong(Object obj, long offset,long expect, long update);
这个⽅法是个内部⽅法,也就是⽤C/C++实现的,所以有兴趣的⾃⼰去看源码,这⾥贴⼀个blog:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。