当前位置:首页 > Java技术 > Java随机数Random解析

Java随机数Random解析

2022年08月05日 15:30:55Java技术6
首先介绍一下随机数生成的概念

Java随机数的核心就是种子,在Random中其实就是一个AtomicLong类型的变量。

private final AtomicLong seed;

随机数的生成依赖于种子,当我们通过new Random()创建一个随机数生成器,会通过计算为我们默认生成一个种子的计算值,再通过这个种子的计算值运算得出最终的种子。下面这段代码是Random默认生成种子计算值的逻辑。

public Random() {
     
    this(seedUniquifier() ^ System.nanoTime());
}

private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);

private static long seedUniquifier() {
     
    for (;;) {
     
        long current = seedUniquifier.get();
        long next = current * 181783497276652981L;
        if (seedUniquifier.compareAndSet(current, next))
            return next;
    }
}

我们也可以在初始化时给定种子的计算值,再通过计算生成最终的种子。

public Random(long seed) {
     
	// 主要就是下面这行代码
    this.seed = new AtomicLong(initialScramble(seed));
}
// 通过计算让种子变得复杂
private static long initialScramble(long seed) {
     
    return (seed ^ multiplier) & mask;
}

111111111111111111111111111111111111111111111111——mask(48位二进制)
10111011110111011001110011001101101——multiplier

—— 总结:随机数的生成依靠种子,种子的生成依靠于种子的计算值,种子的计算值可以由我们给定,也可以由Random默认的通过当前时间的纳秒计算得到,种子的计算值通过一系列的运算得到最终的种子。


下面来介绍随机数是如何生成的

上面提到,随机数的生成依靠于种子,通过一个种子生成出随机数后,那么该种子就不可用了,因为通过该种子生成的随机数还会是一样的,因此需要通过旧的种子生成新的种子,以应对下一次随机数的生成。

下面先看看Random.next()方法,该方法用于生成新的种子。
nextInt(),nextLong()等方法都是通过调用该方法保留种子的固定长度,再通过该部分种子计算出随机数。
eg.int则保留31位,long则保留63位——

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)); // 新的种子保留指定位数的长度
}

下面只举nextInt一个例子,剩下的也只是计算随机数所使用的算法不同。

public int nextInt(int bound) {
     
	int r = next(31); // 保留31位的种子
	int m = bound - 1; // 生成随机数的最大值,也就是不包含bound边界值
	if ((bound & m) == 0) // 如果边界值是2的幂
		// eg. bound = 8, r = xxx,xxx是31位2进制)
		// 1111111111111111111111111111110 * 1000 =
		// 1111111111111111111111111111110000(相当于直接把000加在了原来的二进制后面)
		// 1111111111111111111111111111110000 >> 31 = 111
		r = (int)((bound * (long)r) >> 31);
	else {
     
		for (int u = r;
			 u - (r = u % bound) + m < 0;
			 u = next(31))
			;
	}
	return r;
}

—— 总结:随机数的生成依靠种子,种子的生成依靠于种子的计算值,种子的计算值可以由我们给定,也可以由Random默认的通过当前时间的纳秒计算得到,种子的计算值通过一系列的运算得到最终的种子。每次都需要通过新生成的种子来构建我们的随机数。


Random真的存在线程安全问题吗?

什么叫Random发生了线程安全问题?—— 当我们使用相同的种子去生成随机数,那么这两个随机数会是相同的,也就是如果我们使用多个线程去操作同一个Random,如果多个线程使用相同的种子去生成随机数,那么就会生成一系列相同的随机数。

但Random真的线程不安全吗?

回到之前这个通过旧种子生成新种子的逻辑,多个线程可以同时获取相同的旧种子,但这里通过CAS保证了只有一个线程可以通过旧种子生成新的种子 —— 也就是Random是线程安全的,但因为使用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));
}

由于Random在高并发下效率低,因此考虑使用ThreadLocalRandom。线程会冲突的只有种子,因此只要把种子交给每个线程维护即可,因此结合ThreadLocal就成了ThreadLocalRandom,这里就不介绍了。


下面内容可以不看

通过代码进行测试,下面代码本来是用来测试Random是线程不安全的
(之前对Random印象不深,总是忘记Random在高并发下是效率低的,而不是线程不安全的)

1)创建自定义Random(copy代码),维护一个线程安全的Set(seedSet)用于存储新生成的种子

private Set<Long> seedSet = Collections.synchronizedSet(new HashSet<>());

2)修改next()方法,在成功生成新的种子后,将种子添加到seedSet

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));
    seedSet.add(nextseed);
    return (int)(nextseed >>> (48 - bits));
}

3)自定义Random暴露一个seedSize()方法,用于查看生成的种子数

public int seedSize() {
     
    return seedSet.size();
}

4)下面创建200个线程,每个线程生成1000个随机数存到自己的HashSet,最后对每个线程生成的HashSet进行汇总,最后打印结果。

public class Test {
     

    public static void main(String[] args) throws Exception {
     
        Random random = new Random();
        Set<Integer>[] sets = new Set[200];
        CountDownLatch countDownLatch = new CountDownLatch(200);
        for (int i = 0; i < 200; i++) {
     
            new Thread(new RandomThread(random, sets, i, countDownLatch)).start();
        }
        countDownLatch.await();
        Set<Integer> totalSet = new HashSet<>();
        for (int i = 0; i < 200; i++) {
     
            for (int j : sets[i]) {
     
                totalSet.add(j);
            }
        }
        System.out.println("预计" + (1000 * 200) + ",实际" + totalSet.size() + ",重复了" + ((1000 * 200) - totalSet.size()));
        System.out.println("总共生成的种子数:" + random.seedSize());
    }

}

class RandomThread implements Runnable {
     

    private Random random;
    private Set<Integer>[] sets;
    private int num;
    private CountDownLatch countDownLatch;

    public RandomThread(Random random, Set<Integer>[] sets, int num, CountDownLatch countDownLatch) {
     
        this.random = random;
        this.sets = sets;
        this.num = num;
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
     
        for (int i = 0; i < 1000; i++) {
     
            Set<Integer> set = sets[num];
            if (set == null) {
     
                set = new HashSet<>(1000);
                sets[num] = set;
            }
            set.add(random.nextInt(Integer.MAX_VALUE));
        }
        countDownLatch.countDown();
    }
}

测试多次结果

预计200000,实际199983,重复了17
总共生成的种子数:200000
预计200000,实际199991,重复了9
总共生成的种子数:200000
预计200000,实际199993,重复了7
总共生成的种子数:200000

上述内容,如有错误,欢迎指正

作者:DFYoung
来源链接:https://blog.csdn.net/dh554112075/article/details/104069945

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/17812.html

标签: Java随机数
分享给朋友:

“Java随机数Random解析” 的相关文章

Java随机数Random

记录一个比较操蛋的问题 1、随机数是由随机种子根据一定的计算方法计算出来的结果。只要计算方法一定,随机种子一定,那么计算出来的结果就相同。 2、如果你每次调用srand()时都提供相同的种子值,那么,你将会得到相同的随机数序列 3、Rand...

java产生随机数的方法

一.在j2se里我们可以使用Math.random()方法来产生一个随机数,这个产生的随机数是0-1之间的一个double,我们可以把他乘以一定的数,比如说乘以100,他就是个100以内的随机,这个在j2me中没有。 二.在java.util这个包里面提供了一个...

利用java随机数模拟求π的值

利用java随机数模拟求π的值

我们从小学的时候就已经熟知了π的值是介于3.1415926到3.1415927之间的一个无限不循环小数,今天我们就来利用java中的Math.random()这个随机数产生器来计算π的值,产生的数值范围为0~1。 用随机数来计算π的值可能...

Java 中随机数获取的方案

Java 中随机数获取的方案

一、方法 1、(数据类型)(最小值+Math.random()*(最大值-最小值+1)) 例:(int)(1+Math.random()*(10-1+1)) 从1到10的int型随数 package com; public class test2 {...

Java获取随机数

方法1 (数据类型)(最小值+Math.random()*(最大值-最小值+1)) 例: (int)(1+Math.random()*(10-1+1)) 从1到1...

java byte 随机数

Java中想要生成一个指定范围之内的随机数字通常两种方法:调用 Math 类的 random() 方法 使用 Random 类。 Random 类提供了丰富的随机数生成方法,可以产生 boolean、int、long、float、byte 数组以及 do...

Java 随机数

Java 随机数

Java 随机数   本章先讲解Java随机数的几种产生方式,然后通过示例对其进行演示。 广义上讲,Java中的随机数的有三种产生方式: (01). 通过System.currentTimeMillis(...

Java产生两位随机数

Random random = new Random(); int ends = random.nexIn(99); String.format("%02d",ends);//如果不足两位,前面补0 作者:m0_67400972...

Java 高斯分布随机数

Java 高斯分布随机数

Java 高斯分布随机数 觉得有用的话,欢迎一起讨论相互学习~ 用Random类中的nextGaussian()方法,可以产生标准正态分布的随机数,其中均值为0,方差为1. 而对于 N ( a , σ 2...

java 猜系统获取的随机数

    int randomNumber=(int)(Math.random()*8)+1; 注释是:得到一个1到8之间的随机整数。 /*********************************************************...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。