随机数

5/10/2022 Java

摘要

JDK:1.8.0_202

# 一:Math.random()

# 1.1 源码

Math部分源码:

package java.lang;

public final class Math {

    /**
     * 返回一个带正号的双精度值,大于或等于 0.0 且小于 1.0。 
     * 返回值是伪随机选择的,从该范围(近似)均匀分布。
     * 
     * 当这个方法第一次被调用时,它会创建一个新的伪随机数生成器,就像下面语句一样
     * 		new java.util.Random()
     * 
     * 这个新的伪随机数生成器随后用于对该方法的所有调用,并且不会在其他任何地方使用。
     * 此方法已正确同步,以允许多个线程正确使用。
     * 但是,如果许多线程需要以很高的速率生成伪随机数,
     * 则可能会减少每个线程对拥有自己的伪随机数生成器的争用。
     * 
     * @return 0.0 <= 伪随机 double < 1.0
     */
    public static double random() {
        return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
    }
    
    private static final class RandomNumberGeneratorHolder {
        static final Random randomNumberGenerator = new Random();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

从上面代码不难看出,Math.random() 实际调用 new Random().nextDouble()

Random部分源码:

package java.util;

public class Random implements java.io.Serializable {

	private final AtomicLong seed;
	
	private static final long multiplier = 0x5DEECE66DL;
	
	private static final long addend = 0xBL;
	
    private static final long mask = (1L << 48) - 1;
    
	private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53)

	/**
     * 从该随机数生成器的序列中返回 0.0 到 1.0 之间的下一个伪随机、均匀分布的 double 值。
     * nextDouble 的一般约定是从 0.0d(包括) 到 1.0d(不包括) 范围内(大约)均匀选择一个 double 值,伪随机生成并返回的。
     * 
     * nextDouble 方法由 Random 类实现,就像这样:
     * 		 public double nextDouble() {    
     * 		 	return (((long)next(26) << 27) + next(27))      
     * 		 		/ (double)(1L << 53);  
     * 		}
     * 
     * 
     * [在 Java 的早期版本中,结果被错误地计算为:
     * 		return (((long)next(27) << 27) + next(27))
     * 			/ (double)(1L << 54);
     * 这会造成不均匀性,有效数字低位为0的可能性被提高了三倍]
     * 
     * @return 0.0 <= 伪随机 double < 1.0
     */
    public double nextDouble() {
        return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
    }
    
    /**
     * 生成下一个伪随机数。子类应该覆盖它,因为它被所有其他方法使用。
     * next 的一般约定是它返回一个 int 值,
     * 如果参数位在 1 到 32(含)之间,那么返回值的许多低位将(大约)独立选择的位值,
     * 其中每个是(大约)同样可能是 0 或 1。
     * 方法 next 由 Random 类通过原子更新种子来实现
     * 		(seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1)
     * 返回
     * 		(int)(seed >>> (48 - bits)).
     * 这是一个线性同余伪随机数生成器,由 D. H. Lehmer 定义并由 Donald E. Knuth 在计算机编程艺术,第 3 卷:半数值算法,第 3.2.1 节中描述。
     * 
     * @param  bits 随机位 1~32
     * @return 来自该随机数生成器序列的下一个伪随机值
     */
    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));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 1.2 例子

public class Test {

    public static void main(String[] args) {
        new MyThread("线程A").start();
        new MyThread("线程B").start();
    }

}

class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + Math.random());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 二:Random

# 2.1 常用API

  • nextBoolean() — 返回均匀分布的 true 或者 false
  • nextBytes(byte[] bytes) — 生成随机字节并将它们放入入参的字节数组中, 产生的随机字节数等于字节数组的长度
  • nextDouble() — 返回 0.0 到 1.0 之间的均匀分布的 double
  • nextFloat() — 返回 0.0 到 1.0 之间的均匀分布的 float
  • nextGaussian() — 返回 0.0 到 1.0 之间的高斯分布(即正态分布)的 double
  • nextInt() — 返回均匀分布的 int
  • nextInt(int n) — 返回 0 到 n 之间的均匀分布的 int(包括 0,不包括 n)
  • nextLong() — 返回均匀分布的 long
  • setSeed(long seed) — 设置种子

# 2.2 源码

package java.util;

/**
 * java.util.Random 的实例是线程安全的。
 * 但是,跨线程并发使用同一个 java.util.Random 实例可能会遇到争用,从而导致性能下降。
 * 考虑改为在多线程设计中使用 java.util.concurrent.ThreadLocalRandom。
 * 
 * java.util.Random 的实例在密码学上不是安全的。
 * 考虑改为使用 java.security.SecureRandom 来获取密码安全的伪随机数生成器,以供安全敏感的应用程序使用。
 */
public class Random implements java.io.Serializable {

    /**
     * 从该随机数生成器的序列中返回下一个伪随机、均匀分布的 int 值。 nextInt 的一般约定是伪随机生成并返回一个 int 值。
     * 所有 2<sup>32</sup> 个可能的 int 值都是以(大约)相等的概率产生的。
     *
     * nextInt 方法由 Random 类实现,就像通过:
     * 		public int nextInt() {
     * 			return next(32);
     * 		}
     *
     * @return 来自此随机数生成器序列的下一个伪随机、均匀分布的 int 值
     */
    public int nextInt() {
        return next(32);
    }

	// next() 方法源码上面已经贴出,此处省略
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 2.3 例子

public class Test {

    public static void main(String[] args) {
        Random random1 = new Random(10000);
        Random random2 = new Random(10000);

        for (int i = 0; i < 5; i++) {
            System.out.printf("random1:%d\t random2:%d\n", random1.nextInt(), random2.nextInt());
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12

可以发现,线性同余法伪随机数生成器缺点,可预测,甚至计算出种子值

# 三:ThreadLocalRandom

# 3.1 源码

/**
 * 与 Math 类使用的全局随机生成器一样,
 * ThreadLocalRandom 使用内部生成的种子进行初始化,否则该种子可能不会被修改。 
 * 如果适用,在并发程序中使用 ThreadLocalRandom 而不是共享的 Random 对象通常会遇到更少的开销和争用。
 * 当多个任务(例如,每个 ForkJoinTask)在线程池中并行使用随机数时,使用 ThreadLocalRandom 尤其合适。
 * @since 1.7 可见JDK7之后才提供
 */
public class ThreadLocalRandom extends Random {

	private static final sun.misc.Unsafe UNSAFE;
	
	/**
     * 为当前线程初始化线程字段。仅在 Thread.threadLocalRandomProbe 为零时调用,表示需要生成线程本地种子值。
     * 请注意,即使初始化是纯线程本地的,也需要依赖(静态)原子生成器来初始化值。
     */
    static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
    }

    /**
     * 返回当前线程的 ThreadLocalRandom。
     *
     * @return 返回当前线程的 ThreadLocalRandom。
     */
    public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 3.2 例子

public class Test {

    public static void main(String[] args) {
        new MyThread().start();
        new MyThread().start();
    }

}

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + ThreadLocalRandom.current().nextInt());
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 四:SecureRandom

# 4.1 源码

package java.security;

/**
 * 此类提供了一个加密的强随机数生成器(RNG)
 * 加密强随机数至少符合 FIPS 140-2 加密模块的安全要求中第 4.9.1 节中指定的统计随机数生成器测试。
 * 此外,SecureRandom 必须产生非确定性的输出。因此,传递给 SecureRandom 对象的任何种子材料都必须是不可预测的,并且所有 SecureRandom 输出序列必须具有加密强度,如 RFC 1750:安全随机性建议中所述。
 * 
 * 调用者通过无参数构造函数或 getInstance 方法之一获取 SecureRandom 实例:
 * 		SecureRandom random = new SecureRandom();
 * 
 * 许多 SecureRandom 实现采用伪随机数生成器 (PRNG) 的形式,这意味着它们使用确定性算法从真正的随机种子生成伪随机序列。 
 * 其他实现可能会产生真正的随机数,而其他实现可能会使用这两种技术的组合。
 * 
 * SecureRandom 的典型调用者调用以下方法来检索随机字节:
 * 		SecureRandom random = new SecureRandom();
 * 		byte bytes[] = new byte[20];
 * 		random.nextBytes(bytes);
 * 
 * 调用者还可以调用 generateSeed 方法来生成给定数量的种子字节(例如,为其他随机数生成器提供种子):
 * 		byte seed[] = random.generateSeed(20);
 * 注意:根据实现的不同,generateSeed 和 nextBytes 方法可能会在收集entropy时阻塞,例如,如果它们需要在各种类 Unix 操作系统上从 /dev/random 读取。
 */
public class SecureRandom extends java.util.Random {

	/**
	 * 构造实现默认随机数算法的安全随机数生成器(RNG)。
	 * 此构造函数遍历已注册的安全提供者列表,从最首选的 Provider 开始。
	 * 返回一个新的 SecureRandom 对象,该对象封装来自支持 SecureRandom(RNG) 算法的第一个 Provider 的 SecureRandomSpi 实现。
	 * 如果没有 Provider 支持 RNG 算法,则返回特定于实现的默认值。
	 * 
	 * 注意,可以通过 Security.getProviders() 方法检索已注册 Provider 程序的列表。
	 * 
	 * 有关标准 RNG 算法名称的信息,请参阅 Java Cryptography Architecture Standard Algorithm Name Documentation 中的 SecureRandom 部分。
	 * 
	 * 返回的 SecureRandom 对象尚未设置种子。要对返回的对象设置种子,调用 setSeed 方法。
	 * 如果未调用 setSeed,则第一次调用 nextBytes 将强制 SecureRandom 对象自行设置。
	 * 如果之前调用了 setSeed,则不会发生这种自行设置。
	 */
    public SecureRandom() {
    
    	/*
    	 * 对父类构造函数调用,将导致自动对 setSeed 方法调用,
    	 * 该方法在传递零时立即返回。
    	 */
        super(0);
        getDefaultPRNG(false, null);
    }
    
    private void getDefaultPRNG(boolean setSeed, byte[] seed) {
        String prng = getPrngAlgorithm();
        if (prng == null) {
            // bummer, get the SUN implementation
            prng = "SHA1PRNG";
            this.secureRandomSpi = new sun.security.provider.SecureRandom();
            this.provider = Providers.getSunProvider();
            if (setSeed) {
                this.secureRandomSpi.engineSetSeed(seed);
            }
        } else {
            try {
                SecureRandom random = SecureRandom.getInstance(prng);
                this.secureRandomSpi = random.getSecureRandomSpi();
                this.provider = random.getProvider();
                if (setSeed) {
                    this.secureRandomSpi.engineSetSeed(seed);
                }
            } catch (NoSuchAlgorithmException nsae) {
                // never happens, because we made sure the algorithm exists[永远不会发生,因为我们确保算法存在]
                throw new RuntimeException(nsae);
            }
        }
        /*
         * 基于 JDK 1.1 的实现子类 SecureRandom 而不是 SecureRandomSpi。
         * 他们还将通过此代码路径,因为他们必须调用 SecureRandom 构造函数,
         * 因为它是他们的父类。 如果正在处理这样的实现,
         * 请不要设置算法值,因为它会不准确。
         */
        if (getClass() == SecureRandom.class) {
            this.algorithm = prng;
        }
    }
    
    
    /**
     * 通过查看所有已注册的providers程序来获取默认 PRNG 算法。
     * 返回已注册 SecureRandom 实现的第一个提供程序的第一个 PRNG 算法,
     * 如果没有注册的providers程序提供 SecureRandom 实现,则返回 null
     */
    private static String getPrngAlgorithm() {
        for (Provider p : Providers.getProviderList().providers()) {
            for (Service s : p.getServices()) {
                if (s.getType().equals("SecureRandom")) {
                    return s.getAlgorithm();
                }
            }
        }
        return null;
    }
    
    /**
     * 生成一个包含用户指定数量的伪随机位的整数(右对齐,前导零)。
     * 此方法覆盖 java.util.Random 方法,并用于为从该类继承的所有方法(例如,nextInt、nextLong 和 nextFloat)提供随机位源。
     *
     * @param numBits 要生成的伪随机位数,其中 0 <= numBits <= 32
     * @return 伪随机位的 int
     */
    @Override
    final protected int next(int numBits) {
        int numBytes = (numBits+7)/8;
        byte b[] = new byte[numBytes];
        int next = 0;

        nextBytes(b);
        for (int i = 0; i < numBytes; i++) {
            next = (next << 8) + (b[i] & 0xFF);
        }

        return next >>> (numBytes*8 - numBits);
    }
    
    /**
     * 返回实现指定随机数生成器 (RNG) 算法的 SecureRandom 对象。
     *
     * 此方法遍历已注册的安全provider列表,从最喜欢的provider开始。返回一个新的 SecureRandom 对象,
     * 该对象封装来自支持指定算法的第一个 Provider 的 SecureRandomSpi 实现。
     *
     * 请注意,可以通过 Security.getProviders() 方法检索已注册provider程序的列表。
     * 
     * 返回的 SecureRandom 对象未设置种子。要返回设置种子的对象,调用 setSeed 方法。
     * 如果未调用 setSeed,则第一次调用 nextBytes 将强制 SecureRandom 对象自行设置。如果之前调用了 setSeed,则不会发生。
     *
     * @param algorithm RNG 算法的名称。有关标准 RNG 算法名称的信息,请参阅 Java Cryptography Architecture Standard Algorithm Name Documentation 中的 SecureRandom 部分
     * @return  新的 SecureRandom 对象
     * @exception NoSuchAlgorithmException 如果没有 Provider 支持指定算法的 SecureRandomSpi 实现。
     * @see Provider
     *
     * @since 1.2
     */
    public static SecureRandom getInstance(String algorithm)
            throws NoSuchAlgorithmException {
        Instance instance = GetInstance.getInstance("SecureRandom",
            SecureRandomSpi.class, algorithm);
        return new SecureRandom((SecureRandomSpi)instance.impl,
            instance.provider, algorithm);
    }
    
    public static SecureRandom getInstance(String algorithm, String provider)
            throws NoSuchAlgorithmException, NoSuchProviderException {
        Instance instance = GetInstance.getInstance("SecureRandom",
            SecureRandomSpi.class, algorithm, provider);
        return new SecureRandom((SecureRandomSpi)instance.impl,
            instance.provider, algorithm);
    }
    
    public static SecureRandom getInstance(String algorithm,
            Provider provider) throws NoSuchAlgorithmException {
        Instance instance = GetInstance.getInstance("SecureRandom",
            SecureRandomSpi.class, algorithm, provider);
        return new SecureRandom((SecureRandomSpi)instance.impl,
            instance.provider, algorithm);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

RNG:操作系统收集了一些随机事件,比如鼠标点击,键盘点击等等,SecureRandom 使用这些随机事件作为种子。

# 4.2 例子

  • 仅指定算法名称,如:SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
  • 既指定了算法名称又指定了包提供程序,如:SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
public class Test {

    public static void main(String[] args) throws NoSuchAlgorithmException {
        byte[] b = new byte[]{'a', 'b', 'c'};
        SecureRandom s1 = new SecureRandom(b);
        SecureRandom s2 = new SecureRandom(b);

        for (int i = 0; i < 5; i++) {
            System.out.printf("s1=%d\t s2=%d\n", s1.nextInt(), s2.nextInt());
        }

        System.out.println("================================");

        String string = "SHA1PRNG";
        SecureRandom s3 = SecureRandom.getInstance(string);
        SecureRandom s4 = SecureRandom.getInstance(string);

        for (int i = 0; i < 5; i++) {
            System.out.printf("s3=%d\t s4=%d\n", s3.nextInt(), s4.nextInt());
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 五:Apache

使用 Apache commons-lang3 包中的 RandomUtils 类,Maven依赖如下:

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.12.0</version>
</dependency>
1
2
3
4
5

文档https://hcdtc.github.io/zh/docs/30-development-manual/2-back-end/99-dev-utils/2-common-lang3/10-randomutils/ (opens new window)

# 5.1 源码

package org.apache.commons.lang3;

public class RandomUtils {

    private static final Random RANDOM = new Random();

	/**
	 * 返回指定范围内的随机整数。
	 * @param startInclusive 可以返回的最小值,必须为非负数
	 * @param endExclusive   上限(不包括在内)
	 * @throws IllegalArgumentException 如果 startInclusive > endExclusive 或 startInclusive 为负数
	 * @return 随机整数
	 */
    public static int nextInt(final int startInclusive, final int endExclusive) {
        Validate.isTrue(endExclusive >= startInclusive,
                "Start value must be smaller or equal to end value.");
        Validate.isTrue(startInclusive >= 0, "Both range values must be non-negative.");

        if (startInclusive == endExclusive) {
            return startInclusive;
        }

        return startInclusive + RANDOM.nextInt(endExclusive - startInclusive);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

通过上面的源码可以发现,RandomUtils 内部也是使用 Random

# 5.2 例子

public class Test {

    public static void main(String[] args) {
        // -------------随机布尔值-------------
        boolean flag = RandomUtils.nextBoolean();
        System.out.println("随机布尔值:" + flag);    // 随机布尔值:true

        // -------------随机字节数组-------------
        byte[] numbers = RandomUtils.nextBytes(6);
        System.out.println("随机字节数组:" + ArrayUtils.toString(numbers)); // 随机字节数组:{-59,-54,83,-31,-20,124}

        // -------------随机int型整数-------------
        // 返回一个0 - Integer.MAX_VALUE之间的随机整数
        int number1 = RandomUtils.nextInt();
        // 指定区间内的int型随机整数
        // startInclusive 最小值(包含,非负数)
        // endExclusive 最大值(不包含)
        int number2 = RandomUtils.nextInt(20, 60);
        System.out.println("随机int型整数:" + number1 + " , " + number2); // 随机int型整数:1163955650 , 38

        // -------------随机long型整数-------------
        // 返回一个0 - Long.MAX_VALUE之间的随机整数
        long number3 = RandomUtils.nextLong();
        // 返回一个在指定区间内的long型随机整数
        // startInclusive 最小值(包含,非负)
        // endExclusive 最大值(不包含)
        long number4 = RandomUtils.nextLong(34, 68);
        System.out.println("随机long型整数:" + number3 + " , " + number4); // 随机long型整数:5447152552252703622 , 62

        // -------------随机float型浮点数-------------
        // 返回一个0 - Float.MAX_VALUE之间的随机浮点数
        float number5 = RandomUtils.nextFloat();
        // 返回一个在指定区间内的float型随机浮点数
        // startInclusive 最小值(包含,非负)
        // endExclusive 最大值(不包含)
        float number6 = RandomUtils.nextFloat(23, 56);
        System.out.println("随机float型浮点数:" + number5 + " , " + number6); // 随机float型浮点数:7.601023E37 , 43.483505

        // -------------随机double型浮点数-------------
        // 返回一个0 - Double.MAX_VALUE之间的随机浮点数
        double number7 = RandomUtils.nextDouble();
        // 返回一个在指定区间内的double型随机浮点数
        // startInclusive 最小值(包含,非负)
        // endExclusive 最大值(不包含)
        double number8 = RandomUtils.nextDouble(23.0, 34);
        System.out.println("随机double型浮点数:" + number7 + " , " + number8); // 随机double型浮点数:1.5465889705550356E308 , 30.962661380617195
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

运行截图:

# 六:参考文献

最后更新: 5/12/2022, 10:27:49 AM