java伪随机数生成器

news/2024/11/20 23:35:41/

关于随机数的基本概念

1、对随机数性质分类:

  • 随机性:符合该性质的叫弱伪随机数。这种随机数仅可以用于一般应用,无法用在密码学,例如java中的java.util.Random类
  • 不可预测性:符合该性质的叫强伪随机数。在密码学中,随机数除了随机性还需要不可预测性,也就是无法通过历史数据推测下一个随机数
  • 不可重复性:符合该性质的叫真随机数。仅靠软件无法生成不可重复的随机数,这是因为计算机软件仅具备有限内部状态,只要内部状态相同,必然生成同一个数,这也叫做周期。要满足不可重复性,只能通过物理现象获取,比如温度、声音等,这些需要硬件支持,比如英特尔CPU中提供了一个随机数生成器。

上述性能越往下越严格,且具有包含性质:比如不可重复性一定具有随机性和不可预测性;不可预测性一定具有随机性。

说明:反复投骰子生成的数列,具有不可重复性。

2、总结:

随机数可以通过硬件、软件来生成。通过硬件生成的随机数,例如利用传感器收集的热量、声音等具有无法预测不重复的自然现象,称作真随机数,也叫随机数生成器(Random Number Generator RNG)。

通过软件生成的随机数,叫做为随机数(Pseudo Random Number Generator PRNG)。因为软件状态有限,一定具有周期性。为随机数分为:弱为随机和强为随机。

接下来,我们介绍java中的伪随机数使用。

Random介绍

在JDK7之前java.util.Random是使用比较广泛的随机数生成工具类,另外java.lang.Math中的随机数生成也是使用的java.util.Random的实例。Random类使用的是线性同余算法生成随机数,该算法是一种弱伪随机数算法,不具备不可预测性,所以不能用于密码学。

1、Random类是线程安全的:

Random随机数生成是和种子seed有关,而为了保证线程安全性,Random通过CAS机制来保证线程安全性。从next()方法中我们可以看到seed是通过不断的CAS来进行修改值的。如果在高并发的场景下,那么可能会导致CAS失败,从而导致不断的自旋,这样可能会导致CPU过高。

此外,在创建Random的时候也是用CAS:

这里也可以看到,默认Random的种子适合当前时间有关的,当然也可以在构造函数中指定seed。

 2、使用

Random random = new Random();
random.nextInt(); //返回[0, max)的随机正整数
random.nextInt(10); //返回[0,10)的随机正整数double rd1 = Math.random(); //返回[0.0,1.0)的随机小数

ThreadLocalRandom介绍

ThreadLocalRandom继承Random,也是弱伪随机数。它主要解决了Random在高并发环境下随机数生成性能问题。

1、ThreadLocalRandom为什么线程安全?

和ThreadLocal原理类似,它将随机种子保存在当前Thread对象的threadLocalRandomSeed变量中,这样每个线程都有自己的随机种子,实现了线程级别的隔离,所以ThreadLocalRandom也并不需要像Random通过自旋锁和cas来保证随机种子的线程安全性。在高并发的场景下,效率也会相对较高。

2、源码实现:

ThreadLocalRandom大量使用了unsafe方法来直接通过地址操作变量。关于unfafe用法见:3.0 Java魔法类:Unsafe应用解析.note

1)种子定义在Thread类,在ThreadLocalRandom定义的SEED表示Thread类的threadLocalRandomSeed属性的内存偏移量

ThreadLocalRandom.java
private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {try {UNSAFE = sun.misc.Unsafe.getUnsafe();Class<?> tk = Thread.class;SEED = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomSeed"));PROBE = UNSAFE.objectFieldOffset(tk.getDeclaredField("threadLocalRandomProbe"));} catch (Exception e) {throw new Error(e);}
}Thread.java
@sun.misc.Contended("tlr")
//当前Thread的随机种子 默认值是0
long threadLocalRandomSeed;
@sun.misc.Contended("tlr")
//用来标志当前Thread的threadLocalRandomSeed是否进行了初始化 0代表没有,非0代表已经初始化 默认值是0
int threadLocalRandomProbe;

说明:其中 @sun.misc.Contende 是为了避免伪共享。

2)当线程调用ThreadLocalRandom的current方法,ThreadLocalRandom负责初始化调用线程的threadLocalRandomSeed变量,也就是初始化种子;

public static ThreadLocalRandom current() {// 获取当前线程的if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)localInit();return instance;
}static final void localInit() {int p = probeGenerator.addAndGet(PROBE_INCREMENT);int probe = (p == 0) ? 1 : p; // skip 0long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));Thread t = Thread.currentThread();UNSAFE.putLong(t, SEED, seed);UNSAFE.putInt(t, PROBE, probe);
}

说明:通过unsafe的putLong方法根据对象属性偏移地址直接操作内存。

3)当调用ThreadLocalRandom的nextInt方法时候,实际上是获取当前线程的threadLocalRandomSeed变量作为当前种子来计算新的值,然后更新新的种子到当前线程的threadLocalRandomSeed变量。(线性同余算法:根据种子计算随机数,然后将随机数更新成新的种子供下次计算):

public int nextInt(int bound) {if (bound <= 0)throw new IllegalArgumentException(BadBound);int r = mix32(nextSeed());int m = bound - 1;if ((bound & m) == 0) // power of twor &= m;else { // reject over-represented candidatesfor (int u = r >>> 1;u + m - (r = u % bound) < 0;u = mix32(nextSeed()) >>> 1);}return r;
}
final long nextSeed() {Thread t; long r; // read and update per-thread seedUNSAFE.putLong(t = Thread.currentThread(), SEED,r = UNSAFE.getLong(t, SEED) + GAMMA);return r;
}

并发高的情况下,试试用ThreadLocalRandom来生成随机数 - 腾讯云开发者社区-腾讯云

3、说明:

在ssh架构中,无论是Random、ThreadLocalRandom,都不能放在全局变量中定义,都应该放到方法中去定义、初始化。否则就有可能不同线程使用了相同的种子,这样创建出来的随机数序列可能是一样的。即:

java.Security.SecureRandom介绍

也是继承java.util.Random,该类提供了强伪随机数算法,可以在密码学中使用。

1、Linux 中随机数是如何产生的呢 PRNG(Pseudo-Random Number Generator)?

Linux 内核采用熵来描述数据的随机性,熵(entropy)是描述系统混乱无序程度的物理量,熵越大则说明该系统的有序性越差。内核维护了一个熵池用来收集来自设备驱动程序和其它来源的环境噪音。内核中PRNG 为一个字符设备 random,它提供了 2 个字符设备供用户态进程使用——/dev/random 和/dev/urandom:

  • /dev/random 适用于对随机数质量要求比较高的请求,在熵池中数据不足时, 读取 dev/random 设备时会返回小于熵池噪声总数的随机字节。/dev/random 可生成高随机性的公钥或一次性密码本。若熵池空了,对/dev/random 的读操作将会被阻塞,直到收集到了足够的环境噪声为止。这样的设计使得/dev/random 是真正的随机数发生器,提供了最大可能的随机数据熵。
  • /dev/urandom,非阻塞的随机数发生器,它会重复使用熵池中的数据以产生伪随机数据。这表示对/dev/urandom 的读取操作不会产生阻塞,但其输出的熵可能小于/dev/random 的。它可以作为生成较低强度密码的伪随机数生成器,对大多数应用来说,随机性是可以接受的。

2、获取SecureRandom实例方法:

有两种方式获取SecureRandom实例:

  • SecureRandom random = new SecureRandom(); //也可以通过参数指定种子,但不建议这么做。
  • Random random = SecureRandom.getInstanceStrong();

这两种方式的区别:在linux下,第一种采用的是/dev/urandom作为伪随机数发生器;后者采用的是/dev/random作为伪随机数发生器。所以,后者可以能会因为系统的熵池数据不足导致其调用nextInt等方法获取随机数时阻塞。

1)实验:

import java.security.SecureRandom;
import java.util.Random;
import java.security.NoSuchAlgorithmException;
public class RandomTest {public static void main(String... str) throws NoSuchAlgorithmException {//SecureRandom random = new SecureRandom();Random random = SecureRandom.getInstanceStrong();int out = 0;for (int i = 0; i < 1<<20 ; i++) {out ^= random.nextInt();}System.out.println(out);}
}

该程序分别用两种不同方式创建SecureRandom实例,然后产生1048576次随机数。在linux上经过测试发现:使用第6行方式创建SecureRandom实例,程序在2s内执行完;如果使用第7行方式,程序会阻塞。这也验证了上面的结论。

2)通过strace跟踪系统调用:

strace命令可以跟踪linux的系统调用。所以我们对上面的程序进行如下运行:

A、使用第6行方式创建SecureRandom实例,然后javac编译,通过如下方式运行:

javac RandomTest.java
strace -ff -o ./c.strace  java RandomTest
1839562559
#会在当前目录产生大量的strace文件,通过执行如下命令搜索"dev/random"所在的文件
grep "dev" -r -n ./
./c.strace.22057:6:openat(AT_FDCWD, "/sys/devices/system/cpu", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
./c.strace.22057:966:access("/dev/random", R_OK)             = 0
./c.strace.22057:967:access("/dev/random", R_OK)             = 0
./c.strace.22057:968:access("/dev/urandom", R_OK)            = 0
./c.strace.22057:969:open("/dev/random", O_RDONLY)           = 5
./c.strace.22057:970:fstat(5, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
./c.strace.22057:971:open("/dev/urandom", O_RDONLY)          = 6
./c.strace.22057:972:fstat(6, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
./c.strace.22057:973:access("/dev/random", R_OK)             = 0
./c.strace.22057:974:access("/dev/random", R_OK)             = 0
./c.strace.22057:975:open("/dev/random", O_RDONLY)           = 7
./c.strace.22057:976:fstat(7, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
./c.strace.22057:977:open("/dev/random", O_RDONLY)           = 8
./c.strace.22057:978:fstat(8, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
./c.strace.22057:979:access("/dev/urandom", R_OK)            = 0
./c.strace.22057:980:access("/dev/urandom", R_OK)            = 0
./c.strace.22057:981:open("/dev/urandom", O_RDONLY)          = 9
./c.strace.22057:982:fstat(9, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
./c.strace.22057:985:open("/dev/urandom", O_RDONLY)          = 10
./c.strace.22057:986:fstat(10, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 9), ...}) = 0
./c.strace.21889:96:fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
./c.strace.22065:10:open("/sys/devices/system/cpu/online", O_RDONLY|O_CLOEXEC) = 3

打开文件

可以看到,虽然SecureRandom.java底层会打开/dev/random和/dev/urandom这两个文件,但最终读取的却是6号,也就是/dev/urandom文件。

B、使用第7行方式创建SecureRandom实例,然后javac编译,同样方式运行

strace -ff -o c.strace  java RandomTest
#会一直在这里阻塞住,没有执行结果。。。

同样方式找到文件,打开:

可以看到,SecureRandom.java底层会打开/dev/random和/dev/urandom这两个文件,但最终读取的却是8号,也就是/dev/random文件,同时read系统调用一直阻塞住了。

linux - Java SecureRandom doesn't block? How? - Information Security Stack Exchange

使用 SecureRandom 产生随机数采坑记录 - 腾讯云开发者社区-腾讯云

 

 

 

 

 

 


http://www.ppmy.cn/news/12491.html

相关文章

Redis 实现用户积分和积分排行榜微服务优化

文章目录修改添加积分方法积分排行控制层redis实现积分排行业务逻辑层Redis排行榜测试使用 JMeter 压测对比在之前的博客中我通过 MySQL数据库实现了积分和积分排行榜功能&#xff0c;在数据量大和并发量高的情况下会有以下缺点&#xff1a;SQL编写复杂&#xff1b;数据量大&am…

Pytorch优化器全总结(四)常用优化器性能对比 含代码

目录 写在前面 一、优化器介绍 1.SGDMomentum 2.Adagrad 3.Adadelta 4.RMSprop 5.Adam 6.Adamax 7.AdaW 8.L-BFGS 二、优化器对比 优化器系列文章列表 Pytorch优化器全总结&#xff08;一&#xff09;SGD、ASGD、Rprop、Adagrad Pytorch优化器全总结&#xff08;二…

Python【3】:格式化输出

文章目录前言1. 通过 %\%% 实现1.1. 语法1.2. 常见格式化符号1.3. 格式化操作符辅助指令2. 通过 format() 函数实现2.1. 语法2.2. 用法2.2.1. 基本用法2.2.2. 数字格式化前言 在做题与实际应用中&#xff0c;经常会出现需要格式化&#xff08;输出&#xff09;字符串的情况&am…

走近软件生态系统

生态系统&#xff08;Ecosystem&#xff09;原本是一个生物学术语&#xff0c;意思是由一些生命体相互依存、相互制约而形成的大系统&#xff0c;就像我们学生时代在生物学课堂上学到的那样。隐喻无处不在&#xff0c;人们把这个术语移植到了 IT 领域中来&#xff0c;比如我们常…

【阶段四】Python深度学习09篇:深度学习项目实战:循环神经网络处理时序数据项目实战:CNN和RNN组合模型

本篇的思维导图: 项目背景 时间序列数据集中的所有数据都伴随着一个时戳,比如股票、天气数据。这个数据集,是科学家们多年间用开普勒天文望远镜观察并记录下来的银河系中的一些恒星的亮度。广袤的宇宙,浩瀚的星空在过去很长一段时间里,人类是没有办法证明系外行星…

【Linux系统】第四篇:Linux中编辑器vim的使用

文章目录一、vim的介绍1、vim的基本模式2、vim的使用二、命令模式1、光标移动操作2、文本复制、粘贴、剪切、撤销操作3、文本编辑相关操作三、插入模式四、底行模式底行模式命令集五、vim的配置原理六、sudo无法提权问题一、vim的介绍 vim是Linux下的一款多模式编辑器。 注意…

U-Boot 之零 源码文件、启动阶段(TPL、SPL)、FALCON、设备树

最近&#xff0c;工作重心要从裸机开发转移到嵌入式 Linux 系统开发&#xff0c;在之前的博文 Linux 之八 完整嵌入式 Linux 环境、&#xff08;交叉&#xff09;编译工具链、CPU 体系架构、嵌入式系统构建工具 中详细介绍了嵌入式 Linux 环境&#xff0c;接下来就是重点学习一…

C++ 简单实现RPC网络通讯

RPC是远程调用系统简称&#xff0c;它允许程序调用运行在另一台计算机上的过程&#xff0c;就像调用本地的过程一样。RPC 实现了网络编程的“过程调用”模型&#xff0c;让程序员可以像调用本地函数一样调用远程函数。最近在做的也是远程调用过程&#xff0c;所以通过重新梳理R…