[java 随机数] ThreadLocalRandom、Random

真理具有相对性。
----马克思


文章目录

  • [java 随机数] ThreadLocalRandom、Random
  • 一、什么是随机数: 真随机数? 假随机数?
  • 二、Random和ThreadLocalRandom的区别?
  • 2.1 Random使用以及原理浅析
  • 2.1.1 基础使用
  • 2.1.2源码浅析
  • 2.1.3 Random性能问题
  • 2.2 ThreadLocalRandom使用和原理浅析
  • 2.2.1 基本使用
  • 2.2.2 源码浅析


计算机世界的随机数也是相对性的,计算机不可能还原真实的真正随机。


本篇文章主要解决两个问题:

  1. 什么是随机数: 真随机数? 假随机数?
  2. Random和ThreadLocalRandom的区别和使用场景?

一、什么是随机数: 真随机数? 假随机数?

若将真假随机数可视化,可以得到以下两个随机数

  1. 真随机数图像

java Random的随机数种子 java随机数random原理_java

真正的随机数是使用物理现象产生的:比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等等,这样的随机数发生器叫做物理性随机数发生器,它们的缺点是技术要求比较高。

  1. 假随机数图像

java Random的随机数种子 java随机数random原理_初始化_02

计算机中的随机函数是按照一定算法模拟产生的,其结果是确定的,是可见的。我们可以这样认为这个可预见的结果其出现的概率是100%。所以用计算机随机函数所产生的“随机数”并不随机,是伪随机数。

简而言之: 伪随机数是一组有规律的数,计算机生成的都是伪随机数。但这个规律周期可能会很长,所以在使用的时候不会感觉到周期性。

二、Random和ThreadLocalRandom的区别?

2.1 Random使用以及原理浅析

2.1.1 基础使用
//1.  不带sead的
new Random().nextInt();
/**
public Random() {
		// 使用CAS和当前纳秒时间来获取sead
 this(seedUniquifier() ^ System.nanoTime());
}
*/


// 2. 带sead的使用, sead用于生成
new Random(1000).nextInt();
/**
public Random(long seed) {
 if (getClass() == Random.class)
 		 // initialScramble 方法就是: (seed ^ multiplier) & mask
     this.seed = new AtomicLong(initialScramble(seed));
 else {
		 // 给Random子类用
     this.seed = new AtomicLong();
     setSeed(seed);
 }
}
*/
2.1.2源码浅析

以上是java中Random对的一个简单使用, sead是伪随机数生成的一个关键状态字,示例中的nextInt()会调用next()方法:next(32);

protected int next(int bits) {
    long oldseed, nextseed;
	  // 使用CAS操作来生成nextseed(返回值),不同的线程使用各自实例的sead
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}
2.1.3 Random性能问题

使用Random没有现成安全的问题,但在高负载情况下,多个线程会在上文的CAS操作处争抢设置nextseed ,此时肯定有一些线程执行CAS连续失败,进而导致线程阻塞。

为解决这种线程争抢-堵塞问题,jdk在concurrent包中添加了ThreadLocalRandom类。
可以点击这个文档查看Random和ThreadLocalRandom的性能对比分析(基于JHM): 性能分析

2.2 ThreadLocalRandom使用和原理浅析

2.2.1 基本使用
@Test
 public void testRandom() {
     ThreadLocalRandom localRandom = ThreadLocalRandom.current();
     /*数字*/
     localRandom.nextInt(10); //[0,10)
     localRandom.nextLong(); //返回伪随机long
     localRandom.nextInt(2, 10); //[2,10)
     localRandom.nextDouble(); //[0,1)

     /*streams*/
     DoubleStream doubles = localRandom.doubles();
     doubles.forEach(System.out::println);
     localRandom.ints();
 }
2.2.2 源码浅析

ThreadLocalRandom 只有一个静态方法: current(),用于获取当前线程的ThreadLocalRandom实例。正如下文代码所示,在获取实例的过程中,调用Unsafe方法来初始化一些关键字段: seed、probe。

static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThreadLocalRandom current() {
  		// 未初始化则初始化
      if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
          localInit();
      return instance;
  }
	/**
	初始化当前线程的线程字段。 
	仅在Thread.threadLocalRandomProbe为零时调用,表示需要生成线程本地种子值。
  warn: 即使初始化纯粹是线程局部的,我们也需要依靠(静态)原子生成器来初始化值。
	*/
  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);
  }

其中核心的Unsafe使用代码如下所示,ThreadLocalRandom类的核心逻辑都是通过Unsafe的实例UNSAFE来实现的。此外由于在类加载后内存大小已经确定了,偏移量自然也确定了,因此不会存在修改不合法地址的情况。

// Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
  	        /**
            1. Unsafe.objectFieldOffset 可以获取到属性在对象内存的偏移量
 						2. Unsafe.getDeclaredField 返回一个Field对象,该对象反映此Class对象表示的类或接口的指定声明字段。 如果此Class对象表示数组类型,则建议使用java.lang.Class#getDeclaredFields方法
  	        */
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

以下以nextInt()方法举一个例子来说明ThreadLocalRandom的原理,其中核心操作就是nextSeed()方法

public int nextInt() {
  return mix32(nextSeed());
}

// 对seed进行位移操作
private static int mix32(long z) {
  z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
  return (int)(((z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L) >>> 32);
}
// 使用Unsafe修改SEED变量
final long nextSeed() {
  Thread t; long r; // read and update per-thread seed
  UNSAFE.putLong(t = Thread.currentThread(), SEED,
                 r = UNSAFE.getLong(t, SEED) + GAMMA);
  return r;
}

参考链接

  1. https://zhenbianshu.github.io/2019/12/is_threadlocalrandom_safe.html


  1. https://www.jianshu.com/p/29ae27e401d1