热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析JDK中的四种随机数生成器

我们从jdk8说起。主要是四个随机数生成器。神马?有四个?接下来我们简单说下这几个类的使用场景,来了解其中的细微差别,和a

我们从jdk8说起。主要是四个随机数生成器。神马?有四个?

接下来我们简单说下这几个类的使用场景,来了解其中的细微差别,和api设计者的良苦用心。

https://img3.mukewang.com/5d2847bc00017fec06820236.jpg

java.util.Random
java.util.concurrent.ThreadLocalRandom
java.security.SecureRandom
java.util.SplittableRandom


Random

最常用的就是Random。

用来生成伪随机数,默认使用48位种子、线性同余公式进行修改。我们可以通过构造器传入初始seed,或者通过setSeed重置(同步)。默认seed为系统时间的纳秒数,真大!

如果两个(多个)不同的Random实例,使用相同的seed,按照相同的顺序调用相同方法,那么它们得到的数字序列也是相同的。这看起来不太随机。  这种设计策略,既有优点也有缺点,优点是“相同seed”生成的序列是一致的,使过程具有可回溯和校验性(平台无关、运行时机无关);缺点就是,这种一致性,潜在引入其“可被预测”的风险。

Random的实例是线程安全的。  但是,跨线程并发使用相同的java.util.Random实例可能会遇到争用,从而导致性能稍欠佳(nextX方法中,在对seed赋值时使用了CAS,测试结果显示,其实性能损耗很小)。请考虑在多线程设计中使用ThreadLocalRandom。同时,我们在并发环境下,也没有必要刻意使用多个Random实例。

Random实例不具有加密安全性。  相反,请考虑使用SecureRandom来获取加密安全的伪随机数生成器,以供安全敏感应用程序使用。

Random是最常用的随机数生成类,适用于绝大部分场景。

Random random = new Random(100);
System.out.println(random.nextInt(10) + "," + random.nextInt(30) + "," + random.nextInt(50));random = new Random(100);
System.out.println(random.nextInt(10) + "," + random.nextInt(30) + "," + random.nextInt(50));random = new Random(100);
System.out.println(random.nextInt(10) + "," + random.nextInt(30) + "," + random.nextInt(50));

上述三个不同的random实例,使用了相同的seed。调用过程一样,其中产生的随机数序列也是完全一样的。多次执行结果也完全一致,简单而言,只要初始seed一样,即使实例不同,多次运行它们的结果都是一致的。这个现象与上面所说的一致。

如果Random构造器中不指定seed,而是使用默认的系统时间纳秒数作为主导变量,三个random实例执行的结果是不同的。多次执行结果也不一样。由此可见,seed是否具有随机性,在一定程度上,也决定了Random产生结果的随机性。

所以,在分布式或者多线程环境下,如果Random实例处于代码一致的tasks线程中,可能这些分布式进程或者线程,产出的序列值是一样的。这也是在JDK 7引入ForkJoin的同时,也引入了ThreadLocalRandom类。

ThreadLocalRandom

这个类的作用,使得随机数的生成器隔离到当前线程。此类继承自java.util.Random,与Math类使用的全局Random生成器一样,ThreadLocalRandom使用内部生成的种子进行初始化,否则可能无法修改。

在并发程序中使用ThreadLocalRandom,通常会有更少的开销和竞争。当多个任务(例如,每个ForkJoinTask)在线程池中并行使用随机数时,ThreadLocalRandom是特别合适的。

切记,在多个线程中不应该共享ThreadLocalRandom实例。

ThreadLocalRandom初始化是private的,所以无法通过构造器设定seed,此外其setSeed方法也被重写而不支持(抛出异常)。默认情况下,每个ThreadLocalRandom实例的seed主导变量值为系统时间(纳秒):

private static long initialSeed() {String sec &#61; VM.getSavedProperty("java.util.secureRandomSeed");if (Boolean.parseBoolean(sec)) {byte[] seedBytes &#61; java.security.SecureRandom.getSeed(8);long s &#61; (long)(seedBytes[0]) & 0xffL;for (int i &#61; 1; i <8; &#43;&#43;i)s &#61; (s <<8) | ((long)(seedBytes[i]) & 0xffL);return s;}return (mix64(System.currentTimeMillis()) ^mix64(System.nanoTime()));
}

根据其初始化seed的实现&#xff0c;我们也可以通过JVM启动参数增加“-Djava.util.secureRandomSeed&#61;true”&#xff0c;此时初始seed变量将不再是系统时间&#xff0c;而是由SecureRandom类生成一个随机因子&#xff0c;以此作为ThreadLoalRandom的初始seed。

真是够绕的。

从源码中&#xff0c;我并没有看到Thread-ID作为变量生成seed&#xff0c;而且nextX方法中随机数生成算法也具有一致性。这意味着&#xff0c;如果多个线程初始ThreadLocalRandom的时间完全一致&#xff0c;在调用方法和过程相同的情况下&#xff0c;产生的随机序列也是相同的&#xff1b;在一定程度上“-Djava.util.secureRandom&#61;true”可以规避此问题。

ThreadLocalRandom并没有使用ThreadLocal来支持内部数据存储等&#xff0c;而是直接使用UnSafe操作当前Thread对象引用中seed属性的内存地址并进行数据操作&#xff0c;我比较佩服SUN的这种巧妙的做法。

SecureRandom

它也继承自Random&#xff0c;该类提供加密强随机数生成器&#xff08;RNG&#xff09;&#xff0c;加密强随机数最低限度符合FIPS 140-2“加密模块的安全要求”。此外&#xff0c;SecureRandom必须产生非确定性输出。因此&#xff0c;传递给SecureRandom对象的任何种子材料必须是不可预测的&#xff0c;并且所有SecureRandom输出序列必须具有加密强度。&#xff08;官文&#xff0c;其实我也一知半解&#xff09;

SecureRandom默认支持两种RNG加密算法实现&#xff1a;
SHA1PRNG”算法提供者sun.security.provider.SecureRandom
NativePRNG”提供者sun.security.provider.NativePRNG

默认情况下&#xff0c;是“SHA1PRNG”&#xff0c;即SUN提供的实现。此外可以通过
“-Djava.security&#61;file:/dev/urandom”
&#xff08;推荐&#xff09;或者
“-Djava.security&#61;file:/dev/random”
指定使用linux本地的随机算法&#xff0c;
即NativePRNG&#xff1b;
其中“/dev/random”与“/dev/urandom”在不同unix-*平台中实现有所不同&#xff0c;性能也有所差异&#xff0c;建议使用“/dev/urandom”。

/dev/random的一个副本是/dev/urandom &#xff08;”unlocked”&#xff0c;非阻塞的随机数发生器&#xff09;&#xff0c;它会重复使用熵池中的数据以产生伪随机数据。这表示对/dev/urandom的读取操作不会产生阻塞&#xff0c;但其输出的熵可能小于/dev/random的。它可以作为生成较低强度密码的伪随机数生成器&#xff0c;不建议用于生成高强度长期密码。

算法的内部实现&#xff0c;比较复杂&#xff1b;本人测试&#xff0c;其实性能差不不太大&#xff08;JDK 8环境&#xff09;。SecureRandom也是线程安全的。

从输出结果上分析&#xff0c;无论是否指定SecureRandom的初始seed&#xff0c;单个实例多次运行的结果也完全不同 &#xff1b;多个不同的SecureRandom实例无论是否指定seed&#xff0c;即使指定一样的初始seed&#xff0c;同时运行的结果也完全不同。

SecureRandom继承自Random&#xff0c;但是对nextX方法中的底层方法进行的重写覆盖&#xff0c;不过仍然基于Random的CAS且SecureRandom的底层方法还使用的同步&#xff0c;所以在并发环境下&#xff0c;性能比Random差了一些。

SplittableRandom

JDK 8 新增的API&#xff0c;主要适用于Fork/join形式的跨线程操作中。它并没有继承java.util.Random类。

具有相同seed的不同SplittableRandom实例或者同一个SplittableRandom&#xff0c;多次运行结果是一致的。这和Random是一致的。

非线程安全&#xff0c;不能被并发使用。 &#xff08;不会报错&#xff0c;但是并发时可能多个线程同时得到相同的随机数&#xff09;

同ThreadLocalRandom&#xff0c;对“-Djava.util.secureRandom&#61;true”参数支持&#xff0c;但是只有使用默认构造器的时候&#xff0c;才会使用SecureRandom辅助生成初始seed。即不指定初始seed时&#xff0c;同一个SplittableRandom实例多次运行&#xff0c;或者不同的实例运行&#xff0c;结果是不同的。

其中有一个split()方法&#xff0c;用来构造并返回与新的实例&#xff0c;这个实例共享了一些不可变状态。需要注意&#xff0c;split产生的新SplittableRandom实例&#xff0c;与原实例并不存在内部数据的并发竞争&#xff0c;也不会交替或者延续原实例的随机数生成序列&#xff08;即两个实例产出随机序列的一致性&#xff0c;与原实例没有关系&#xff0c;只是在统计值层面更加接近&#xff09;&#xff1b;但是代码一致性的情况下&#xff0c;多次运行&#xff0c;其随机数序列的结果总是一致的&#xff08;假如初始seed是指定的&#xff0c;而非默认&#xff09;&#xff0c;这一点与Random、ThreadLocalRandom表现相同。

public SplittableRandom split() {return new SplittableRandom(nextLong(), mixGamma(nextSeed()));
}

样例代码。

System.out.println("第一段");
SplittableRandom random &#61; new SplittableRandom(100);
Thread thread &#61; new Thread(new Runnable() {&#64;Overridepublic void run() {SplittableRandom _random &#61; random.split();for (int i&#61;0; i <5; i&#43;&#43;) {System.out.println("---" &#43; _random.nextInt(100));}}
});
thread.start();
thread.join();
for (int i&#61;0; i <5; i&#43;&#43;) {System.out.println("&#43;&#43;&#43;" &#43; random.nextInt(100));
}System.out.println("第二段");
SplittableRandom _random &#61; new SplittableRandom(100);
for (int i&#61;0; i <10; i&#43;&#43;) {System.out.println("..." &#43; _random.nextInt(100));
}

执行结果。

第一段
---71
---85
---10
---60
---98
&#43;&#43;&#43;44
&#43;&#43;&#43;87
&#43;&#43;&#43;77
&#43;&#43;&#43;67
&#43;&#43;&#43;72第二段
...92
...30
...44
...87
...77
...67
...72
...23
...9
...64

从执行结果上看&#xff0c;split产生的random实例与原实例执行结果上没有相似之处&#xff1b;但是不同SplittableRandom实例&#xff08;无论是否执行过split&#xff09;&#xff0c;其产出随机数序列是一致的。

性能检测

简析&#xff0c;基准&#xff1a;100000随机数&#xff0c;单线程

1、 Random &#xff1a;2毫秒
2、 ThreadLocalRandom &#xff1a;1毫秒
3、 SecureRandom
1&#xff09;默认算法&#xff0c;即SHAR1PRNG&#xff1a;80毫秒左右。
2&#xff09;NativePRNG&#xff1a;90毫秒左右。
4、 SplittableRandom &#xff1a;1毫秒


End

平常使用Random&#xff0c;或者大多数时候使用&#xff0c;都是没有问题的&#xff0c;它也是线程安全的。SplittableRandom是内部使用的类&#xff0c;应用较少&#xff0c;即使它也是public的也掩饰不了偏门。ThreadLocalRandom是为了在高并发环境下节省一点细微的时间&#xff0c;追求性能的应用推荐使用。而对于有安全需求的&#xff0c;又希望更随机一些的&#xff0c;用SecureRandom再好不过了。

jdk竟然有这么多随机数生成器。有没有大吃一精&#xff1f;我反正是跪了。


作者&#xff1a;小姐姐味道
链接&#xff1a;http://www.imooc.com/article/289297
来源&#xff1a;慕课网
本文原创发布于慕课网 &#xff0c;转载请注明出处&#xff0c;谢谢合作


推荐阅读
  • Linux环境下进程间通信:深入解析信号机制
    本文详细探讨了Linux系统中信号的生命周期,从信号生成到处理函数执行完毕的全过程,并介绍了信号编程中的注意事项和常见应用实例。通过分析信号在进程中的注册、注销及处理过程,帮助读者理解如何高效利用信号进行进程间通信。 ... [详细]
  • 深入理解Java多线程并发处理:基础与实践
    本文探讨了Java中的多线程并发处理机制,从基本概念到实际应用,帮助读者全面理解并掌握多线程编程技巧。通过实例解析和理论阐述,确保初学者也能轻松入门。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 本文详细介绍了Hive中用于日期和字符串相互转换的多种函数,包括从时间戳到日期格式的转换、日期到时间戳的转换,以及如何处理不同格式的日期字符串。通过这些函数,用户可以轻松实现日期和字符串之间的灵活转换,满足数据处理中的各种需求。 ... [详细]
  • 本文详细介绍了Java的安装、配置、运行流程以及有效的学习方法,旨在帮助初学者快速上手Java编程。 ... [详细]
  • 本文将详细介绍如何在ThinkPHP6框架中实现多数据库的部署,包括读写分离的策略,以及如何通过负载均衡和MySQL同步技术优化数据库性能。 ... [详细]
  • 本文介绍了如何在 C# 和 XNA 框架中实现一个自定义的 3x3 矩阵类(MMatrix33),旨在深入理解矩阵运算及其应用场景。该类参考了 AS3 Starling 和其他相关资源,以确保算法的准确性和高效性。 ... [详细]
  • Java多线程实现:从1到100分段求和并汇总结果
    本文介绍如何使用Java编写一个程序,通过10个线程分别计算不同区间的和,并最终汇总所有线程的结果。每个线程负责计算一段连续的整数之和,最后将所有线程的结果相加。 ... [详细]
  • 本文将详细探讨 Java 中提供的不可变集合(如 `Collections.unmodifiableXXX`)和同步集合(如 `Collections.synchronizedXXX`)的实现原理及使用方法,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • Go语言以其简洁的语法和强大的并发处理能力而闻名,特别是在云计算和分布式计算领域有着广泛的应用。本文将深入探讨Go语言中的Channel机制,包括其不同类型及其在实际编程中的应用。 ... [详细]
  • 前言无论是对于刚入行工作还是已经工作几年的java开发者来说,面试求职始终是你需要直面的一件事情。首先梳理自己的知识体系,针对性准备,会有事半功倍的效果。我们往往会把重点放在技术上 ... [详细]
  • MySQL锁机制详解
    本文深入探讨了MySQL中的锁机制,包括表级锁、行级锁以及元数据锁,通过实例详细解释了各种锁的工作原理及其应用场景。同时,文章还介绍了如何通过锁来优化数据库性能,避免常见的并发问题。 ... [详细]
  • 本文探讨了Web开发与游戏开发之间的主要区别,旨在帮助开发者更好地理解两种开发领域的特性和需求。文章基于作者的实际经验和网络资料整理而成。 ... [详细]
  • 利用Python实现自动化群发邮件
    本文详细介绍如何使用Python语言来实现邮件的自动群发功能,适合希望提高工作效率的技术爱好者和开发者。 ... [详细]
  • 本文深入探讨Java编程语言的关键特性,包括但不限于其简洁性、强大的面向对象能力、跨平台兼容性、安全机制、高效性能及多线程支持等方面。文章旨在为开发者提供全面理解Java特性的指导。 ... [详细]
author-avatar
手机用户2502927615
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有