Java随机数的使用

前言

本节介绍下Java中随机数生成的方式

一、Random

特点:

  1. 线程安全,虽然共享该实例是线程安全的,但会因竞争同一 seed导致的性能下降
  2. 使用cas保证线程安全

使用方法

Random random = new Random();
int randomInt = random.nextInt(10);// [0,10) 内的随机数
int randomInt2 = random.nextInt(); // 生成一个int值

基本原理

伪随机,生成的随机数有一定规律。

构造函数:

在构造方法当中,根据当前时间的种子生成了一个 AtomicLong 类型的 seed

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

private static long seedUniquifier() {
        // L'Ecuyer, "Tables of Linear Congruential Generators of
        // Different Sizes and Good Lattice Structure", 1999
        for (;;) {
            long current = seedUniquifier.get();
            long next = current * 181783497276652981L;
            if (seedUniquifier.compareAndSet(current, next))
                return next;
        }
}

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

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

nextInt 与 nextInt(int)

public int nextInt() {
        return next(32); // 传入的 32,代指的是 Int 类型的位数。
}

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));// cas 
        // 根据需要的位数返回
        return (int)(nextseed >>> (48 - bits));
    }
    
public int nextInt(int bound) {
        if (bound <= 0)
            throw new IllegalArgumentException(BadBound);
        // 首先获取 31 位的随机数,注意这里是 31 位,和上面 32 位不同,因为在 nextInt() 方法中可以获取到随机数可能是负数,而 nextInt(int bound) 规定只能获取到 [0,bound) 之前的随机数,也就意味着必须是正数,预留一位符号位,所以只获取了31位。(不要想着使用取绝对值这样操作,会导致性能下降)
        int r = next(31);
        int m = bound - 1;
        if ((bound & m) == 0)  // i.e., bound is a power of 2 如果 bound 是2的幂次方,可以直接将第一步获取的值乘以 bound 然后右移31位,解释一下:如果 bound 是4,那么乘以4其实就是左移2位,其实就是变成了33位,再右移31位的话,就又会变成2位,最后,2位 int 的范围其实就是 [0,4) 了。
            r = (int)((bound * (long)r) >> 31);
        else { // 如果不是 2 的幂,通过模运算进行处理。
            for (int u = r;
                 u - (r = u % bound) + m < 0;
                 u = next(31))
                ;
        }
        return r;
}    

并发下,CAS效率不高可以在 JDK7 之前,需要编码保证每个线
程持有一个单独的 Random 实例。在 JDK1.7 之后,Java 提供了更好的解决方案 ThreadLocalRandom

二、ThreadLocalRandom

特点

  1. 线程安全 seed不是全局变量 而是Thread中的三个变量

使用方法

ThreadLocalRandom.current().nextInt();
ThreadLocalRandom.current().nextInt(10);

基本原理

current():

public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
        // 进行初始化
            localInit();
        return instance;
    }

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

其中Thread类中有专门为ThreadLocalRandom专门服务的属性

/** The current seed for a ThreadLocalRandom */
    @sun.misc.Contended("tlr")
    long threadLocalRandomSeed;

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;

    /** Secondary seed isolated from public ThreadLocalRandom sequence */
    @sun.misc.Contended("tlr")
    int threadLocalRandomSecondarySeed;

threadLocalRandomSeed:ThreadLocalRandom 使用它来控制随机数种子。

threadLocalRandomProbe:ThreadLocalRandom 使用它来控制初始化。

threadLocalRandomSecondarySeed:二级种子。

使用@sun.misc.Contended注解处理伪共享问题

用于制造随机数的方法nextInt()

public int nextInt() {
        return mix32(nextSeed());
    }
    
final long nextSeed() {
        Thread t; long r; // read and update per-thread seed
        // 可以看见由于我们每个线程各自都维护了种子,这个时候并不需要 CAS,直接进行 put,在这里利用线程之间隔离,减少了并发冲突;相比较 ThreadLocal<Random>,ThreadLocalRandom 不仅仅减少了对象维护的成本,其内部实现也更轻量级。
        UNSAFE.putLong(t = Thread.currentThread(), SEED,
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
    }    

ThreadLocalRandom 切记不要调用 current 方法之后,作为共享变量使用。每次直接使用就行了。

public class Test {
    // seed 是维护在线程中的,ThreadLocalRandom只是作为一个计算工具
    ThreadLocalRandom wrongRandom = new ThreadLocalRandom().current();
    
    public int test(){
        return wrongRandom.nextInt();
    }
}

References

原文地址:https://www.cnblogs.com/wei57960/p/13923397.html