多线程下获取随机数的小技巧

前言

Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。 Random 实例包括 java.util.Random 的实例或者 Math.random() 的方式。可以直接使用 ThreadLocalRandom


1.Random

Random 使用相同的 seed 创建了两个实例,并且对每个实例进行了相同的方法调用序列,则它们将生成并返回相同的数字序列。

Tip:

Random中的seed是一个Long类型的原子变量AtomicLong对象

使用如下:

Random random = new Random();
for (int i = 0; i < 10; i++) {
	System.out.println("第" + i + "次:" + random.nextInt(10));
}
0:11:12:73:74:45:06:57:58:89:4

这里使用了Random的nextInt方法获取随机数,源码如下:

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

上面的源码中根据老种子生成新种子的实现方式如下

protected int next(int bits) {
	// 得到Long类型的原子变量AtomicLong对象
	AtomicLong seed = this.seed;
	// 声明老种子和新种子变量
	long oldseed;
	long nextseed;
    do {
    	// 获取原子变量种子的值
    	oldseed = seed.get();
    	// 根据当前种子计算出新种子的值
        nextseed = oldseed * 25214903917L + 11L & 281474976710655L;
     	// 使用新的种子去更新老的种子
    } while(!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> 48 - bits);
}

上面两段代码可以看出:

  • 根据老种子生成新种子;
  • 根据新种子计算出随机数。

通常使用时可直接使用 Math 类中的 random() 方法。

2.Math.random()

使用如下:

for (int i = 0; i < 10; i++) {
	// Math.random()生成的是一个double类型的值,这里强转为int型输出
	int random = (int) (Math.random() * 10);
	System.out.println("第" + i + "次:" + random);
}
0:11:32:93:04:25:46:47:48:99:2

random()来源

public final class Math {
		
	// 其余代码省略......
	
    public static double random() {
        return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
    }
	
	private static final class RandomNumberGeneratorHolder {
        static final Random randomNumberGenerator = new Random();
    }
	
	// 其余代码省略......
}

3.ThreadLocalRandom

使用如下:

ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
	System.out.println("第" + i + "次:" + threadLocalRandom.nextLong(10));
}
0:71:12:33:04:05:36:77:78:89:6

ThreadLocalRandom继承了Random并重写了nextInt方法,当调用ThreadLocalRandom的current方法时获取到ThreadLocalRandom的实例,如果线程第一次调用 current() 方法时就会执行 localInit()方法,localInit()中为线程初始化了 seed,并保存在 Unsafe 里,。实现如下:

 public static ThreadLocalRandom current() {
	// 如果线程第一次调用 current() 方法,执行 localInit()方法
    if (U.getInt(Thread.currentThread(), PROBE) == 0) {
    	localInit();
	}
    return instance;
}

再来看看localInit()方法

static final void localInit() {
	int p = probeGenerator.addAndGet(-1640531527);
    int probe = p == 0 ? 1 : p;
    long seed = mix64(seeder.getAndAdd(-4942790177534073029L));
    Thread t = Thread.currentThread();
    U.putLong(t, SEED, seed);
    U.putInt(t, PROBE, probe);
}

ThreadLocalRandom中的nextInt方法如下:

public int nextInt(int bound) {
	// 验证边界的合法性
    if (bound <= 0) {
    	throw new IllegalArgumentException("bound must be positive");
	} else {
		// 根据当前线程中种子计算新种子
        int r = mix32(this.nextSeed());
        // 根据新种子和bound计算随机数
        int m = bound - 1;
        if ((bound & m) == 0) {
        	r &= m;
    	} else {
    		for(int u = r >>> 1; u + m - (r = u % bound) < 0; u = mix32(this.nextSeed()) >>> 1) {
            }
        }
    	return r;
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蒙同學

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值