Java之Random和ThreadLocalRandom

本文详细分析了Java中`Random`类在多线程环境下的问题,即多个线程共享种子导致的随机数不随机现象。为解决这一问题,JDK7引入了`ThreadLocalRandom`,它通过每个线程维护自己的种子变量,避免了线程竞争,提高了并发性能。`ThreadLocalRandom`通过`ThreadLocal`机制确保每个线程拥有独立的随机数生成器,从而实现线程安全的随机数生成。

前言

JDK7之前,java.util.Random都是使用比较广泛的随机数生成类,但是在多线程下的缺陷,也让JDK7之后再JUC包下新增了ThreadLocalRandom

Random相关

先从源码的角度来看看java.util.Random类的使用方法:

// 构造函数1:
public Random() {
	this(seedUniquifier() ^ System.nanoTime());
}

// 构造函数2:
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);
  }
}

从构造函数2来看,使用Random类需要一个随机种子,如果不指定则会在默认构造函数1中生成一个默认的与系统时间相关的值,有了默认的种子后,如何生成随机数呢?

来看看nextInt(...)方法:

public int nextInt(int bound) {
  // 1. 校验参数,小于0则抛出异常
  if (bound <= 0)
    throw new IllegalArgumentException(BadBound);
  // 2. 获取下一个种子,该方法的代码在下面列出来
  int r = next(31);
  // 3. 根据新的种子计算随机数
  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;
}

由上面代码可知,新的随机数生成需要两个步骤:

  1. 根据老的种子生成新的种子
  2. 根据新生成的种子来计算新的随机数

那么问题来了,在单线程情况下,步骤2:int r = next(31)会按照期望的执行流程执行,先算出新的种子,接下来根据步骤3的固定逻辑算出新的随机数,

但是多线程情况下,会出现多个线程可能都拿同一个老的种子去执行步骤2,让后根据固定逻辑步骤3计算随机数,这样会导致多个线程算出来的随机数相同,这显然不是我们想要的。

所以步骤2必须要保证原子性,即当第一个线程在根据老的种子执行完步骤2计算出新的种子后,其他的线程应该把自己老的种子丢弃掉,然后使用第一个线程新生成的种子来计算自己的新种子,以此类推。

只有保证了这个,才能在保证多线程下产生的随机数是随机的,下面列出了相关代码

protected int next(int bits) {
  long oldseed, nextseed;
  AtomicLong seed = this.seed;
  // 使用旧的种子计算新的种子,这里使用了CAS
  do {
    oldseed = seed.get();
    nextseed = (oldseed * multiplier + addend) & mask;
  } while (!seed.compareAndSet(oldseed, nextseed));
  return (int)(nextseed >>> (48 - bits));
}

总结:每个Random实例里面都有一个原子性种子变量用来记录当前的种子值,当要生成新的随机数时,需要根据当前种子计算新的种子并更新回原子变量。在多线程下使用单个Random实例生成随机数时,多个线程会竞争同一个原子变量的更新操作,所以使用了CAS自旋操作,同时只有一个线程会成功,这样造成了大量的线程占用CPU资源空旋,降低了并发性能。

所以ThreadLocalRandom应运而生。

ThreadLocalRandom相关

先看看如何使用:

ThreadLocalRandom random = ThreadLocalRandom.current();
System.out.println(random.nextInt(5));

其中通过ThreadLocalRandom.current()来获取当前线程的随机数生成器。下面来分析ThreadLocalRandom的实现原理。

首先Random则是多个线程共享一个种子,所以在使用了非阻塞CAS来保证同时只有一个线程会成功。

Random多线程示例图.png

从名称上来看,ThreadLocalRandomThreadLocal相似:ThreadLocal通过让每一个线程复制一份变量,使得在每个线程对变量进行操作时实际上是操作自己本地内里面的副本,从而避免了对共享变量进行同步。

实际上ThreadLocalRandom的实现也是这个原理,而ThreadLocalRandomThreadLocalRandom对比来看,种子并不是放在ThreadLocalRandom自身中的一个属性,其实ThreadLocalRandom只是一个工具类,真正的种子则是放在了Thread中,这一点下面看源码的时候会讲。

ThreadLocalRandom多线程示例图.png

这样之后,每个线程只会维护自己的一个种子变量,每个线程只会根据自己老的种子生成新的种子,然后使用新的种子生成随机数,就不会存在竞争问题了,这里点个赞。

说到这里先看看ThreadLocalRandom的UML:

ThreadLocalRandomUML.png

由上图的UML来看,ThreadLocalRandom是继承了Random,但是并没有使用Random类中的seed原子种子变量,而是将种子放在了Thread中的threadLocalRandomSeed属性上,而且重写了nextInt方法。

上文提到过ThreadLocalRandom就是一个工具类,所以当线程调用current()时,会初始化调用线程的ThreadLocalRandom变量,也就是初始化种子。

相关源码:

private static final sun.misc.Unsafe UNSAFE;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
  try {
    // ThreadLocalRandom类也是属于java.rt包下的,通过bootstrap类加载器加载,所以可以直接获取
    UNSAFE = sun.misc.Unsafe.getUnsafe();
    Class<?> tk = Thread.class;
    // 获取Thread类里面threadLocalRandomSeed变量在Thread实例里面的偏移量
    SEED = UNSAFE.objectFieldOffset
      (tk.getDeclaredField("threadLocalRandomSeed"));
    // 获取Thread类里面threadLocalRandomProbe变量在Thread实例里面的偏移量
    PROBE = UNSAFE.objectFieldOffset
      (tk.getDeclaredField("threadLocalRandomProbe"));
    // 获取Thread类里面threadLocalRandomSecondarySeed变量在Thread实例里面的偏移量
    SECONDARY = UNSAFE.objectFieldOffset
      (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
  } catch (Exception e) {
    throw new Error(e);
  }
}

current()方法

// instance是一个静态变量
static final ThreadLocalRandom instance = new ThreadLocalRandom();
public static ThreadLocalRandom current() {
  if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
    localInit();
  return instance; // 返回一个ThreadLocalRandom实例,静态变量
}

localInit()方法

static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    int probe = (p == 0) ? 1 : p; // skip 0
    // 初始化时生成种子,并将并将种子通过Unsafe设置到Thread的threadLocalRandomSeed变量
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    UNSAFE.putLong(t, SEED, seed);
    UNSAFE.putInt(t, PROBE, probe);
}

这里需要说明的一点是,instance是一个静态变量,多个线程共享,所以在instance里面只是放置了一写线程安全的公共操作方法,所以不会涉及到线程安全问题。而在Thread中的threadLocalRandomSeed只是一个普通的long类型,这是因为每个线程维护一个属于自己的种子变量,不存在竞争了,这点原理和ThreadLocal相似。

生成随机数的方式和上文讲的Random的原理相似,所以每个线程在第一次调用current()方法初始化种子后,之后都会根据属于自己的旧的种子生成新的种子,然后通过新的种子生成新的随机数,并将新的种子重置设置在Thread实例中的threadLocalRandomSeed属性上:

// 生成新的种子并使用Unsafe的putLong更新到threadLocalRandomSeed属性
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;
}

总结

本文主要分析了Random生成随机数的原理和其在多线程下需要竞争原子种子变量更新操作的缺点,并分析了JDK7之后ThreadLocalRandom类与RandomThread之间的关系和原理,其让每个线程都持有了一个本地的种子变量,该种子变量只有在使用随机数的时候才会被初始化,多线程下计算新种子是根据自己所维护的种子变量进行更新,避免了多线程之间的竞争。

<think>首先,用户的问题是:“这个随机数由什么生成?ThreadLocalRandom random = ThreadLocalRandom.current();” 这是一个关于JavaThreadLocalRandom类的问题。用户想知道通过ThreadLocalRandom.current()生成的随机数是由什么生成的。 关键点: - ThreadLocalRandomJava中的一个类,用于生成随机数,特别适合在多线程环境中使用。 - ThreadLocalRandom.current()返回当前线程的ThreadLocalRandom实例。 - 它基于ThreadLocal机制,每个线程有自己的随机数生成器实例,避免线程竞争。 - 底层使用的是线性同余生成器(Linear Congruential Generator, LCG)或其他算法,具体取决于Java实现。 在Java中,ThreadLocalRandom继承自Random类,并使用类似的方法生成随机数。它使用一个种子(seed)来初始化,并基于特定的算法生成伪随机数序列。 标准回答: - ThreadLocalRandom.current()返回当前线程的ThreadLocalRandom实例。 - 这个实例使用每个线程独立的种子状态,以避免同步开销。 - 随机数的生成基于伪随机数生成算法,如线性同余生成器(LCG)。 - 在Java 8及更高版本中,ThreadLocalRandom使用了一个改进的算法,可能是基于XORShift或类似的高效算法。 现在,我需要用中文回答,因为用户指定了“请使用中文回答问题”。 回答结构: 1. 直接回答问题。 2. 解释ThreadLocalRandom的工作原理。 3. 然后,根据规则,在回答后必须提出3-5个相关问题。 4. 相关问题必须以“§§相关问题§§”开头,且不能换行分散。 相关问题: - 必须基于用户的问题我的回答。 - 用户的问题是:“这个随机数由什么生成?” - 我的回答将涉及ThreadLocalRandom、种子、算法等。 - 所以,相关问题可以围绕随机数生成、多线程Java实现等。 脑力 storm 相关问题: 1. ThreadLocalRandomRandom类的主要区别是什么? 2. 在多线程环境中,为什么使用ThreadLocalRandom比使用共享的Random实例更好? 3. ThreadLocalRandom的种子是如何初始化的? 4. Java中的伪随机数生成算法有哪些常见类型? 5. 如何确保ThreadLocalRandom生成的随机数是线程安全的? 确保有3-5个问题。 完整响应格式: - 先回答问题。 - 然后,输出“§§相关问题§§”在一行。 - 接着列出问题,每个问题前用数字点号,如“1. ...” - 问题之间换行,但“§§相关问题§§”不能换行分散,意思是它必须是一个连续的字符串开头。 在示例中,是“§§相关问题§§”后直接跟问题列表,没有换行分散,但问题列表可以换行。 在示例: ``` §§相关问题§§ 1. ··· 2. ··· 3. ··· ``` 所以,输出时: - 回答结束。 - 然后新行输出“§§相关问题§§” - 然后新行输出问题列表。 现在,构建回答。 中文回答: 用户的问题:这个随机数由什么生成?ThreadLocalRandom random = ThreadLocalRandom.current(); 回答: 在Java中,`ThreadLocalRandom.current()` 返回当前线程的 ThreadLocalRandom 实例。这个实例用于生成伪随机数。随机数的生成基于每个线程独立的种子状态,使用伪随机数生成算法,如线性同余生成器(LCG)或更高效的变体(如XORShift)。这种设计避免了多线程环境中的同步开销,提高了性能。 更详细: - 种子:每个线程的ThreadLocalRandom实例在第一次使用时初始化种子,通常基于系统时间或其他熵源。 - 算法:Java的具体实现可能使用改进的LCG或其他算法。在Java 8中,ThreadLocalRandom使用了类似于SplittableRandom的算法。 确保回答准确。 然后,相关问题: 1. ThreadLocalRandom Random 类在生成随机数时有何不同? 2. 在多线程应用中,为什么 ThreadLocalRandom 能提供更好的性能? 3. ThreadLocalRandom 的种子是如何初始化管理的? 4. Java 中还有哪些其他随机数生成器类? 5. 伪随机数生成算法中,线性同余生成器的工作原理是什么? 选择3-5个。我选4个。 最终响应: - 回答部分。 - 代码块:用户的问题中有代码,但我的回答不需要代码块,除非必要。用户的问题是文本,不是要求代码。 - 在回答中,我可以解释,但不需要代码块。 - 然后“§§相关问题§§” - 问题列表。</think>在Java中,`ThreadLocalRandom.current()` 返回当前线程的ThreadLocalRandom实例,用于生成伪随机数。这些随机数是由一个伪随机数生成算法生成的,具体基于每个线程独立的种子(seed)状态。在Java标准库中(如Java 8及以上版本),ThreadLocalRandom使用高效的算法实现,例如改进的线性同余生成器(Linear Congruential Generator, LCG)或类似XORShift的变体。种子通常在实例首次使用时初始化,结合系统时间、线程ID等熵源,确保每个线程的随机序列独立且无竞争,从而优化多线程性能。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值