• 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

  • 相关阅读:
    WinForm 防止因为各种因素的操作导致主窗体冻结、卡死的解决方法
    winform常用知识点
    数字金额转为大写金额(C#)
    中关村网站产品参数页的参数纠错的制作
    牛腩自制TXT文本分割工具
    delphi连接SQL2005做的数据库管理系统的一些部署问题
    我的WIN7 RC+汉化安装步骤
    求一整数的所有拆分方式
    全角字符与半角字符的相互转换(C#)
    一道.NET题
  • 原文地址:https://www.cnblogs.com/wei57960/p/13923397.html
Copyright © 2020-2023  润新知