Java-随机数

分类

  • 真随机数。通过物理实验得出。比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等

  • 伪随机数。通过一定算法和种子得出。软件实现的是伪随机数

    • 强伪随机数。难以预测的随机数
    • 弱伪随机数。易于预测的随机数

随机数的特性

  • 随机性。不存在统计学偏差,完全是杂乱的数列
  • 不可预测性。不能从过去的数列推测出下一个要出现的数
  • 不可重现性。

弱伪随机数只需要满足随机性即可。

强伪随机数需要满足随机性和不可预测性。

真随机数需要同时满足三个特性。

应用场景

  • 验证码生成
  • SessionId/Token 生成
  • 密码

伪随机数的生成

伪随机数的生成实现一般是算法+种子

Pseudo Random Number Generator

伪随机数生成器(PRNG)

  • 线性同余法
  • 单向散列函数法
  • 密码法

常见的是线性同余法,Java 中的 Random 类。

种子的选取

算法可以有很多种,伪随机数的强弱主要取决于种子。

比如 Random 的种子是系统当前的毫秒,所以它的随机数是可以预测的。

比如 SecureRandom 的种子在 Linux 下是选取 /dev/random 的、而它是手机计算机的各种信息得到的。比如键盘的输入、内存的使用状态、各种中断。

Linux操作系统的/dev/random设备接口

Windows操作系统的CryptGenRandom接口

Java 中的随机数

Random

如果不指定种子、则选取当前时间作为种子。

    public Random(long seed) {
        if (getClass() == Random.class)
            this.seed = new AtomicLong(initialScramble(seed));
        else {
            // subclass might have overriden setSeed
            this.seed = new AtomicLong();
            setSeed(seed);
        }
    }
    

	public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
		
		// 根据旧种子生成新的种子
        int r = next(31);
        int m = bound - 1;
		// 根据种子生成随机数
        if ((bound & m) == 0)  // i.e., bound is a power of 2
            r = (int)((bound * (long)r) >> 31);
        else {
            for (int u = r;
                 u - (r = u % bound) + m < 0;
                 u = next(31))
                ;
        }
        return r;
    }

通过 CAS 生成一个新的种子

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));
}

ThreadLocalRandom

    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 two
            r &= m;
        else { // reject over-represented candidates
            for (int u = r >>> 1;
                 u + m - (r = u % bound) < 0;
                 u = mix32(nextSeed()) >>> 1)
                ;
        }
        return r;
    }

依赖 ThreadLocal

final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                   r = UNSAFE.getLong(t, SEED) + GAMMA);
    return r;
}
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

我们去 Thread 类中

Random 和 ThreadLocalRandom 比较

SecureRandom

SecureRandom 默认支持两种加密算法:

SHA1PRNG 算法,提供者 sun.security.provider.SecureRandom。Window 下

NativePRNG 算法,提供者 sun.security.provider.NativePRNG。Linux 下

/dev/random和/dev/urandom

/dev/random和/dev/urandom是unix系统提供的产生随机数的设备,很多应用都需要使用random设备提供的随机数,比如ssh keys, SSL keys, TCP/IP sequence numbers 等等。

# 输出所有的值
cat /dev/random | od -x
# 输出总熵
cat /proc/sys/kernel/random/entropy_avail

阻塞问题

soloar代码检测

// 原代码
public void doSomethingCommon() {
  Random rand = new Random();  // Noncompliant; new instance created with each invocation
  int rValue = rand.nextInt();
  //...
// 提出的建议
private Random rand = SecureRandom.getInstanceStrong();  // SecureRandom is preferred to Random

public void doSomethingCommon() {
  int rValue = this.rand.nextInt();
  //...

代码复现

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class TestRandom {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        System.out.println("start.....");
        long start = System.currentTimeMillis();
        SecureRandom random = SecureRandom.getInstanceStrong();

        for(int i = 0; i < 100; i++) {
            System.out.println("第" + i + "个随机数.");
            random.nextInt(10000);
        }
        System.out.println("finish...time/ms:" + (System.currentTimeMillis() - start));
    }
}

Windows 和 Mac 无法复现。Linux 会出现阻塞

jstack 看线程和栈

"main" #1 prio=5 os_prio=0 tid=0x00007f894c009000 nid=0x1129 runnable [0x00007f8952aa9000]
   java.lang.Thread.State: RUNNABLE
	at java.io.FileInputStream.readBytes(Native Method)
	at java.io.FileInputStream.read(FileInputStream.java:255)
	at sun.security.provider.NativePRNG$RandomIO.readFully(NativePRNG.java:424)
	at sun.security.provider.NativePRNG$RandomIO.ensureBufferValid(NativePRNG.java:525)
	at sun.security.provider.NativePRNG$RandomIO.implNextBytes(NativePRNG.java:544)
	- locked <0x000000076c77cb28> (a java.lang.Object)
	at sun.security.provider.NativePRNG$RandomIO.access$400(NativePRNG.java:331)
	at sun.security.provider.NativePRNG$Blocking.engineNextBytes(NativePRNG.java:268)
	at java.security.SecureRandom.nextBytes(SecureRandom.java:468)
	at java.security.SecureRandom.next(SecureRandom.java:491)
	at java.util.Random.nextInt(Random.java:390)
	at TestRandom.main(TestRandom.java:12)



这里注意到是它会持有一个 Java 锁、其他线程调用该方法时会获取不到锁、然后阻塞

// name of the pure random file (also used for setSeed())
private static final String NAME_RANDOM = "/dev/random";
// name of the pseudo random file
private static final String NAME_URANDOM = "/dev/urandom";

private static RandomIO initIO(final Variant v) {
    return AccessController.doPrivileged(
        new PrivilegedAction<RandomIO>() {
            @Override
            public RandomIO run() {
                File seedFile;
                File nextFile;

                switch(v) {
                //...忽略中间代码
                case BLOCKING: // blocking状态下从/dev/random文件中读取
                    seedFile = new File(NAME_RANDOM);
                    nextFile = new File(NAME_RANDOM);
                    break;

                case NONBLOCKING: // unblocking状态下从/dev/urandom文件中读取数据
                    seedFile = new File(NAME_URANDOM);
                    nextFile = new File(NAME_URANDOM);
                    break;
               	//...忽略中间代码
                try {
                    return new RandomIO(seedFile, nextFile);
                } catch (Exception e) {
                    return null;
                }
            }
    });
}

// constructor, called only once from initIO()
private RandomIO(File seedFile, File nextFile) throws IOException {
    this.seedFile = seedFile;
    seedIn = new FileInputStream(seedFile);
    nextIn = new FileInputStream(nextFile);
    nextBuffer = new byte[BUFFER_SIZE];
}

private void ensureBufferValid() throws IOException {
     long time = System.currentTimeMillis();
     if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
         return;
     }
     lastRead = time;
     readFully(nextIn, nextBuffer);
     buffered = nextBuffer.length;
}

直接尝试读取 /dev/random 文件。

同样会产生阻塞

import java.io.FileInputStream;
import java.io.IOException;

public class TestReadUrandom {

    public static void main(String[] args) throws IOException {
        System.out.println("start.....");
        for(int i = 0; i < 100; i++) {
            System.out.println("第" + i + "次读取随机数");
            FileInputStream inputStream = new FileInputStream("/dev/random");
            byte[] buf = new byte[32];
            inputStream.read(buf, 0, buf.length);
        }
    }

}

c 语言读取。同样阻塞

int main() {
    int randnum = 0;
    int fd = open("/dev/random", O_RDONLY);
    if(fd == -1) {
        printf("open error.\n");
        return 1;
    }
    int i = 0;
    for(i = 0; i < 100; i++) {
        read(fd, (char *)&randnum, sizeof(int));
        printf("random number = %d\n", randnum);
    }
    close(fd);
    return 0;
}
  • 不推荐使用SecureRandom.getInstanceStrong()方式获取SecureRandom(除非对随机要求很高)

  • 推荐使用new SecureRandom()获取SecureRandom, linux下从/dev/urandom读取. 虽然是伪随机, 但大部分场景下都满足。或者使用 SecureRandom.getInstance(“NativePRNGNonBlocking”)

虚拟机环境下和服务器情况类似,输入设备操作很少,相对于 Host 而言,Disk I/O 也相对较少,因此依赖 Guest 自身 PRNG 产生的随机数质量不高,因此虚拟机通常从 Host(宿主机)获取部分随机数据。对于 KVM 虚拟机来说,存在一个半虚拟化设备 virtio-rng 作为硬件随机数产生器。Linux Kernel 从 2.6.26 开始支持 virtio-rng, QEMU 在 1.3 版本加入了对 virtio-rng 的支持。 virtio-rng 设备会读取 Host 的随机数源并且填充到 Guest(客户机)的熵池中。

https://juejin.cn/post/6844903572883128327

https://juejin.cn/s/安全随机数的要求是不可预测%2C绝对安全

https://juejin.cn/post/6844904096785235982

https://blog.csdn.net/fengye_csdn/article/details/120843570

https://blog.csdn.net/weixin_45244678/article/details/106137948

https://hongjiang.info/java8-nativeprng-blocking/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: jmu-java-随机数-使用蒙特卡罗法计算圆周率的值 蒙特卡罗法是一种通过随机抽样来解决问题的方法。在计算圆周率的问题中,我们可以通过随机生成点的方式来模拟落在圆内的点和落在正方形内但不在圆内的点的数量,从而得到圆的面积和正方形的面积,进而计算出圆周率的值。 在使用Java编写程序时,我们可以利用Java中的随机数生成器来生成随机点的坐标,然后通过判断点是否在圆内来计算圆内和圆外的点的数量。最后,根据圆的面积和正方形的面积的比例,计算出圆周率的值。 这种方法虽然简单,但是需要生成大量的随机点才能得到较为准确的结果。因此,在实际应用中,需要根据具体问题的要求来确定生成随机点的数量。 ### 回答2: 蒙特卡罗法是一种基于随机数的数值解法,其大致思想是利用概率统计原理构造随机过程,并利用随机过程解决计算问题。在计算圆周率时,可以使用蒙特卡罗法来估算其值。 具体来说,我们可以通过生成随机数的方式来模拟在一个正方形内部随机投点,将正方形视作一个包含在其中的圆的外接正方形,那么正方形内部的点有多少个落在圆的内部,就可以用这个数量来近似出圆的面积。根据圆的公式,可以通过这个近似面积来计算出圆的半径和周长,从而得出圆周率的值。 在实现上,我们可以使用Java中的Math类中的随机数函数来生成一堆随机点,然后统计其中有多少个点落在圆的内部。最终得到的比率就是圆的面积和正方形面积的比例,用这个比例乘以4就可以得到圆周率的值了。 然而,需要注意的是,这种随机投点的方法是一种概率性的计算方法,随着投点数量的增加,估算出的圆周率值会越来越接近实际值。因此,我们需要在程序中设置投点数量,以达到较为准确的计算结果。 总的来说,使用蒙特卡罗法计算圆周率的值是一种在计算中有很大应用价值的技术,特别适用于大量的模拟计算和参数估算。在Java中,通过使用Math类中的随机数函数,可以快速便捷地实现这种方法。 ### 回答3: JMU-Java是一种集成了Java语言与数学算法的库,通过使用随机数生成器可以使用蒙特卡罗法计算圆周率的值。这种方法利用了概率统计的思想,在一个较大的正方形内随机生成若干个点,并统计落在圆内的点的数量,然后通过数学公式计算圆周率的值。这个方法的精确度可以通过增加正方形内的点数来提高,因此随着正方形中点数的增大,计算出的圆周率会越来越接近真实值。 使用JMU-Java库进行计算时,可以通过以下步骤实现: 首先需要导入库文件,然后设置随机数发生器并选择生成点的数量和正方形的边长。然后,利用循环语句和if语句进行判断,统计出正方形内等分的子正方形和落在圆内的点的数量,然后通过公式计算出圆的面积和圆周率的值,并将结果输出。 使用蒙特卡罗法计算圆周率的优点是计算简单易懂,并且可以通过增加点的数量提高计算精确度。但是,它也存在一些缺点,如计算误差较大,需要大量计算,因此会消耗较多的时间和计算资源。另外,在使用时需要注意数据的范围和运算精度,以免影响计算结果的准确性。 总之,蒙特卡罗法计算圆周率的方法是一种简单、直观、易于实现的方法。通过使用JMU-Java库,可以方便地实现该方法,并且在学习编程和数学等方面都具有一定的帮助作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值