从零自学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. cxGrid控件过滤排序和TClientDataSet同步

    https://www.cnblogs.com/false/archive/2013/02/24/2924240.html procedure TReport10Form.cxGridViewData ...

  2. ECCV 2016 paper list

    摘录ECCV2016部分文章,主要有Human pose esimation,  Human activiity / actions, Face alignment, Face detection & ...

  3. .net framework 4.5 +steeltoe+ springcloud(三)实现Hystrix断路器

    在基于.net framework的服务客户端实现断路器功能,基本项目创建步骤可以参照我的另一篇发现和调用服务的笔记,地址:http://www.cnblogs.com/troytian/p/8621 ...

  4. Kali Linux渗透测试实战 1.4 小试牛刀

    目录 1.4 小试牛刀 1.4.1 信息搜集 whois查询 服务指纹识别 端口扫描 综合性扫描 1.4.2 发现漏洞 1.4.3 攻击与权限维持 小结 1.4 小试牛刀 本节作为第一章的最后一节,给 ...

  5. golang简单实现jwt验证(beego、xorm、jwt)

    程序目录结构 简单实现,用户登录后返回一个jwt的token,下次请求带上token请求用户信息接口并返回信息. app.conf文件内容(可以用个beego直接读取里面的内容)写的是一个jwt的se ...

  6. [R]关于R语言的绘图函数

    1. 首先就是plot(x,y,...) 参数: x: 所绘图形横坐标构成的对象 y: 所绘图形纵坐标构成的对象 type: 指定所绘图形类型 pch: 指定绘制点时使用的符号 cex: 指定符号的大 ...

  7. Python 爬虫(二十五) Cookie的处理--cookielib库的使用

    Python中cookielib库(python3中为http.cookiejar)为存储和管理cookie提供客户端支持. 该模块主要功能是提供可存储cookie的对象.使用此模块捕获cookie并 ...

  8. MariaDB 连接查询与子查询(6)

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,MySQL由于现在闭源了,而能轻松成为MySQ ...

  9. 02_python_while循环/格式化输出/逻辑运算

    一. while循环 1.基本形式 while 条件: 循环体 # 判断条件是否为真,如果真,执行代码块.然后再次判断条件是否为真.如果真继续执行代码块...直到条件变成了假.循环退出 ps:死循环 ...

  10. Android必学之AsyncTask

    AsyncTask,即异步任务,是Android给我们提供的一个处理异步任务的类.通过此类,可以实现UI线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给UI线程. .为什么需要使用异步任务 ...