从零自学java消遣一下,看书有点脑阔疼,不如看看源码!(๑╹◡╹)ノ"""

​ JS中Math调用的都是本地方法,底层全是用C++写的,所以完全无法观察实现过程,Java的工具包虽然也有C/C++的介入,不过也有些是自己实现的。

​ 本篇文章主要简单阐述Math.random()的实现过程。

​ Math隶属于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();
return randomNumberGenerator.nextDouble();
} // ...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 = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(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 unsafe.compareAndSwapLong(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 => 0111...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.println(b.getValue());
// 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 = seed.get();
nextseed = (oldseed * multiplier + addend) & mask;
} while (!seed.compareAndSet(oldseed, nextseed));
return (int)(nextseed >>> (48 - bits));
}

​ 方法内部声明了2个long类型种子:oldseed、nextseed,通过get方法取得之前位运算得到的seed赋值给oldseed,然后再次通过运算得到一个nextseed的值,并传给seed.compareAndSet(oldseed, nextseed)方法中。

​ 关于这个方法,源码里是这样的:

// java.util.concurrent.atomic.AtomicLong;
public class AtomicLong extends Number implements java.io.Serializable {
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
}
// sun.misc.Unsafe.java
public native boolean compareAndSwapLong(Object obj, long offset,long expect, long update);

​ 这个方法是个内部方法,也就是用C/C++实现的,所以有兴趣的自己去看源码,这里贴一个blog:

http://www.cnblogs.com/Mainz/p/3546347.html

​ 方法的用处简单讲也很简单,比较oldseed与内存中预期的值,如果符合,就将nextseed放进去。

​ 这里的运算也不管具体数值,oldseed * multiplier按最大计算会出现溢位,截取成long类型后的大小不确定,所以按照与运算这里的范围依然是0 ~ mask,即0 ~ 2^48-1。

​ 最后返回(int)(nextseed >>> (48 - bits)),这里对结果进行类型处理,贴一个类型范围图:

基本类型 最小值 最大值
byte -2^7 2^7 - 1
short -2^15 2^15 - 1
int -2^31 2^31 - 1
long -2^63 2^63 - 1

​ 若结果是大于int类型最大值,超出的部分会被直接截取砍掉。

​ 最后看nextDouble的计算式:

(((long)(next(26)) << 27) + next(27)) / (double)(1L << 53)

​ 传入的bits分别为26与27,这时返回的随机数为:

(int)(nextseed >>> 22) 与 (int)(nextseed >>> 21)

​ >>>为无符号右移,具体意思就不解释了。

​ 得到的结果范围大概是 0 ~ 2^26(27)-1,理论上在这里是不会超过int的最大值。

​ 当seed(测试代码中的tmp)为mask时,此时计算会达到最大值:

(((long)(1L << 53)-1 ) / (double)(1L << 53)

​ 测试代码:

public class test {
public static void main(String [] args){
testb bb = new testb();
long a = (long)bb.getNext(26);
long b = bb.getNext(27);
double c = 1L << 53;
double d = ((a<<27) +b)/c;
// 0.99999999...
System.out.println(d);
}
}
class testb{
long tmp = (1L<<48)-1;
// long tmp = 0 => 0.0
int getNext(int num){
return (int)(tmp >>> (48 - num));
}
}

​ 当测试代码中tmp为0时,计算结果为最小值0。

​ 每一次调用nextDouble,会生成不一样的seed,也就会返回不一样的数字。

​ 这样就是整个随机数生成过程。

​ 完结,撒花ヽ(゚∀゚)メ(゚∀゚)ノ

浅析Java源码之Math.random()的更多相关文章

  1. 浅析Java源码之LinkedList

    可以骂人吗???辛辛苦苦写了2个多小时搞到凌晨2点,点击保存草稿退回到了登录页面???登录成功草稿没了???喵喵喵???智障!!气! 很厉害,隔了30分钟,我的登录又失效了,草稿再次回滚,不客气了,* ...

  2. 浅析Java源码之ArrayList

    面试题经常会问到LinkedList与ArrayList的区别,与其背网上的废话,不如直接撸源码! 文章源码来源于JRE1.8,java.util.ArrayList 既然是浅析,就主要针对该数据结构 ...

  3. 浅析Java源码之HttpServlet

    纯粹是闲的,在慕课网看了几集的Servlet入门,刚写了1个小demo,就想看看源码,好在也不难 主要是介绍一下里面的主要方法,真的没什么内容啊~ 源码来源于apache-tomcat-7.0.52, ...

  4. 浅析Java源码之HashMap

    写这篇文章还是下了一定决心的,因为这个源码看的头疼得很. 老规矩,源码来源于JRE1.8,java.util.HashMap,不讨论I/O及序列化相关内容. 该数据结构简介:使用了散列码来进行快速搜索 ...

  5. 浅析Java源码之HashMap外传-红黑树Treenode(已鸽)

    (这篇文章暂时鸽了,有点理解不能,点进来的小伙伴可以撤了) 刚开始准备在HashMap中直接把红黑树也过了的,结果发现这个类不是一般的麻烦,所以单独开一篇. 由于红黑树之前完全没接触过,所以这篇博客相 ...

  6. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

  7. 24点扑克牌游戏——(含java源码)(GUI实现)

    给出四个数字,要求,在其间添加运算符和括号,使得计算结果等于24. 括号的放置即为决定哪几个数先进行计算.所以,我们先确定首先进行计算的两个相邻的数,计算完成后,就相当于剩下三个数字,仍需要在它们之间 ...

  8. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  9. 从Java源码到Java字节码

    Java最主流的源码编译器,javac,基本上不对代码做优化,只会做少量由Java语言规范要求或推荐的优化:也不做任何混淆,包括名字混淆或控制流混淆这些都不做.这使得javac生成的代码能很好的维持与 ...

随机推荐

  1. RxSwift学习笔记5:Binder

    使用 Binder 创建观察者 //Observable序列(每隔1秒钟发出一个索引数) let scheduleObservable = Observable<Int>.interval ...

  2. MySql and Oracle Data Type Mappings

    the default settings used by SQL Developer to convert data types from MySQL to Oracle. SQL Developer ...

  3. SQLSERVER CXPACKET 等待

    --SQLSERVER CXPACKET 等待 2013-6-11 2 --联机丛书: 3 --当尝试同步查询处理器交换迭代器时出现.如果针对该等待类型的争用成为问题时,可以考虑降低并行度 4 5 6 ...

  4. ASP.NET MVC使用SignalR统计在线用户人数

    学到新东西就记录一下.也许正好有人需要~~~~~~ 由于需要记录当前在线用户,emmmm又是没做过的... 本来想用数据库的形式,但是想想这么简单的功能百度肯定有.遨游一波百度,有所收获.... 虽然 ...

  5. 过滤器中获取form表单或url请求数据

    var httpFormData = filterContext.HttpContext.Request.Form; var logContent = string.Empty; //获取url的 l ...

  6. SAS Shortcut Keys

    最常用的键有F3(运行代码).F6(查看log信息).Ctrl + / (注释代码).Ctrl + Shift + / (取消代码注释) SAS系统一共有4类快捷键,其中部分有重复, 第一类可自定义. ...

  7. dell 远程管理卡的使用racadm

    尊重作者的劳动,转载请注明作者及原文地址 http://www.cnblogs.com/txwsqk/p/6522854.html 可以直接在浏览器输入管理卡的地址-用户名-密码页面操作 也可以通过命 ...

  8. 【UML】:时序图

    时序图表达了类之间调用关系,以及调用时序关系. Actor: 调用者实例化类的对象,执行者. Lifeline: 生命线,竖的虚线.上方方框是类名表示存在的时间,从上至下表示时间流逝.Lifeline ...

  9. Cookies与session的区别

    Cookies 机制 Cookies是服务器在本地机器上存储的一段文本,并随每一个请求发送至同一个服务器. IETF RFC2965 HTTP State Management Mechanism 是 ...

  10. VS2015 WPF Prism Xaml Designer error

    Ref: http://wiki.tk2kpdn.com/build-error-prism5-interactionrequesttrigger-with-vs2015/ gacutil -i &q ...