首页 > 代码库 > 关于Java随机数

关于Java随机数

Java提供两种类型的随机数发生器

1.伪随机数发生器

伪随机数发生器采用特定的算法,将随机数种子seed转换成一系列的伪随机数。伪随机数依赖于seed的值,给定相同的seed值总是生成相同的随机数。伪随机数的生成过程只依赖CPU,不依赖任何外部设备,生成速度快,不会阻塞。

Java提供的伪随机数发生器有java.util.Random类和java.util.concurrent.ThreadLocalRandom类。

Random类采用AtomicLong实现,保证多线程的线程安全性,但正如该类注释上说明的,多线程并发获取随机数时性能较差。

多线程环境中可以使用ThreadLocalRandom作为随机数发生器,ThreadLocalRandom采用了线程局部变量来改善性能,这样就可以使用long而不是AtomicLong,此外,ThreadLocalRandom还进行了字节填充,以避免伪共享。

2.强随机数发生器

强随机数发生器依赖于操作系统底层提供的随机事件。强随机数生成器的初始化速度和生成速度都较慢,而且由于需要一定的熵累积才能生成足够强度的随机数,所以可能会造成阻塞。熵累积通常来源于多个随机事件源,如敲击键盘的时间间隔,移动鼠标的距离与间隔,特定中断的时间间隔等。所以,只有在需要生成加密性强的随机数据的时候才用它。

Java提供的强随机数发生器是java.security.SecureRandom类,该类也是一个线程安全类,使用synchronize方法保证线程安全,但jdk并没有做出承诺在将来改变SecureRandom的线程安全性。因此,同Random一样,在高并发的多线程环境中可能会有性能问题。

在linux的实现中,可以使用/dev/random和/dev/urandom作为随机事件源。由于/dev/random是堵塞的,在读取随机数的时候,当熵池值为空的时候会堵塞影响性能,尤其是系统大并发的生成随机数的时候,如果在随机数要求不高的情况下,可以去读取/dev/urandom来避免阻塞,方法是通过设置参数

-Djava.security.egd=file:/dev/urandom

但这样由于jdk的一个bug,实际上需要这样指定这个值

-Djava.security.egd=file:/dev/./urandom

原因是,在 sun.security.provider.SunEntries类,seedSource先读取系统参数java.security.egd,如果值为空的时候,读取java.security配置文件中的参数securerandom.source, 在通常情况下,就是读取参数securerandom.source,默认值是/dev/urandom。

sun.security.provider.SeedGenerator

final static String URL_DEV_RANDOM = SunEntries.URL_DEV_RANDOM;
final static String URL_DEV_URANDOM = SunEntries.URL_DEV_URANDOM;
    
if (egdSource.equals(URL_DEV_RANDOM) || egdSource.equals(URL_DEV_URANDOM)) {
    try {
        instance = new NativeSeedGenerator();
        if (debug != null) {
            debug.println("Using operating system seed generator");
        }
    } catch (IOException e) {
        if (debug != null) {
            debug.println("Failed to use operating system seed "
                          + "generator: " + e.toString());
        }
    }
} else if (egdSource.length() != 0) {
    try {
        instance = new URLSeedGenerator(egdSource);
        if (debug != null) {
            debug.println("Using URL seed generator reading from "
                          + egdSource);
        }
    } catch (IOException e) {
        if (debug != null)
            debug.println("Failed to create seed generator with "
                          + egdSource + ": " + e.toString());
    }
}

在代码中可以看到当配置值是file:/dev/random或者file:/dev/urandom的时候,启用NativeSeedGenerator, 而在linux下的NativeSeedGenerator类的实现是这样的

class NativeSeedGenerator extends SeedGenerator.URLSeedGenerator {
    NativeSeedGenerator() throws IOException {
        super();
    }
}

而URLSeedGenerator的默认构造方法是

URLSeedGenerator() throws IOException {
    this(SeedGenerator.URL_DEV_RANDOM);
}

也就是说哪怕设置了-Djava.security.egd=file:/dev/urandom,最后的结果一样是读取file:/dev/random,解决办法就是使用linux的多种路径表示法,即使用file:/dev/./urandom来绕过这个问题。



关于Java随机数