Java 中 ThreadLocalRandom 使用指南

更新于 2025-12-29

1. 概述

生成随机值是一项非常常见的任务,因此 Java 提供了 java.util.Random 类。

然而,该类在多线程环境中性能不佳。

简而言之,Random 在多线程环境下表现差的原因在于竞争(contention)——多个线程共享同一个 Random 实例。

为了解决这一限制,Java 在 JDK 7 中引入了 java.util.concurrent.ThreadLocalRandom 类,用于在多线程环境中高效地生成随机数。

接下来,我们将探讨 ThreadLocalRandom 的性能优势,以及如何在实际应用中使用它。


2. ThreadLocalRandom 相较于 Random 的优势

ThreadLocalRandom 可以看作是 ThreadLocalRandom 的结合(稍后详述),并且它是当前线程隔离的。因此,通过避免对 Random 实例的并发访问,它在多线程环境中实现了更优的性能。

一个线程获取的随机数不会受到其他线程的影响,而 java.util.Random 提供的是全局共享的随机数序列。

此外,与 Random 不同,ThreadLocalRandom 不支持显式设置种子(seed)。它重写了从 Random 继承的 setSeed(long seed) 方法,一旦调用就会抛出 UnsupportedOperationException

2.1 线程竞争问题

我们已经知道 Random 类在高并发环境下表现不佳。为了更好地理解这一点,让我们看看其核心方法之一 next(int) 的实现:

private final AtomicLong seed;

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

    return (int)(nextseed >>> (48 - bits));
}

这是线性同余生成器(Linear Congruential Generator, LCG)算法的 Java 实现。很明显,所有线程都共享同一个 seed 实例变量。

为了生成下一组随机比特位,它首先尝试通过 compareAndSet(简称 CAS)原子地修改共享的 seed 值。

当多个线程同时使用 CAS 更新 seed 时,只有一个线程能成功更新,其余线程失败并不断重试,直到成功更新并生成随机数。

该算法是无锁的(lock-free),允许多个线程并发执行。但在高竞争场景下,频繁的 CAS 失败和重试会显著降低整体性能。

相比之下,ThreadLocalRandom 完全消除了这种竞争,因为每个线程都有自己的 Random 实例(更准确地说,是自己的种子),彼此互不干扰。

接下来,我们看看如何使用 ThreadLocalRandom 生成 intlongdouble 类型的随机值。


3. 使用 ThreadLocalRandom 生成随机值

根据 Oracle 官方文档,只需调用 ThreadLocalRandom.current() 方法,即可获得当前线程的 ThreadLocalRandom 实例。然后就可以调用该实例的方法来生成随机值。

无界随机整数

int unboundedRandomValue = ThreadLocalRandom.current().nextInt();

有界随机整数(指定范围)

例如,生成一个介于 0(含)到 100(不含)之间的随机整数:

int boundedRandomValue = ThreadLocalRandom.current().nextInt(0, 100);

注意:下界是包含的,上界是不包含的。

类似地,也可以通过 nextLong()nextDouble() 方法生成 longdouble 类型的随机值。

Java 8 还新增了 nextGaussian() 方法,用于生成均值为 0.0、标准差为 1.0 的高斯(正态)分布随机数。

Random 类似,ThreadLocalRandom 也支持 doubles()ints()longs() 方法,用于生成随机数流(Stream)。


4. 使用 JMH 对比 ThreadLocalRandom 与 Random 的性能

下面我们通过 JMH(Java Microbenchmark Harness)对比在多线程环境下两种方式的性能。

使用共享的 Random 实例

ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<Integer>> callables = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 1000; i++) {
    callables.add(() -> {
        return random.nextInt();
    });
}
executor.invokeAll(callables);

JMH 性能测试结果:

# Run complete. Total time: 00:00:36
Benchmark                                            Mode Cnt Score    Error    Units
ThreadLocalRandomBenchMarker.randomValuesUsingRandom avgt 20  771.613 ± 222.220 us/op

使用 ThreadLocalRandom

ExecutorService executor = Executors.newWorkStealingPool();
List<Callable<Integer>> callables = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    callables.add(() -> {
        return ThreadLocalRandom.current().nextInt();
    });
}
executor.invokeAll(callables);

JMH 测试结果:

# Run complete. Total time: 00:00:36
Benchmark                                                       Mode Cnt Score    Error   Units
ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom avgt 20  624.911 ± 113.268 us/op

性能对比结论

  • 使用 Random 生成 1000 个随机数平均耗时约 772 微秒
  • 使用 ThreadLocalRandom 平均耗时约 625 微秒

由此可见,在高并发场景下,ThreadLocalRandom 明显更高效。

想深入了解 JMH?请参阅我们之前的文章。


5. 实现细节

ThreadLocalRandom 理解为 ThreadLocalRandom 的组合是一种不错的思维模型。事实上,在 Java 8 之前,这种理解与其实现基本一致。

但从 Java 8 开始,这种对应关系被彻底打破ThreadLocalRandom 变成了一个单例(singleton)

以下是 Java 8+ 中 current() 方法的实现:

static final ThreadLocalRandom instance = new ThreadLocalRandom();

public static ThreadLocalRandom current() {
    if (U.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();

    return instance;
}

虽然共享一个全局 Random 实例在高竞争下性能不佳,但为每个线程分配一个完整的 Random 实例也是一种资源浪费。

实际上,每个线程只需要维护自己的**种子值(seed)**即可。

因此,从 Java 8 起,Thread 类本身被改造,直接内嵌了种子字段:

public class Thread implements Runnable {
    // 省略其他代码

    @jdk.internal.vm.annotation.Contended("tlr")
    long threadLocalRandomSeed;

    @jdk.internal.vm.annotation.Contended("tlr")
    int threadLocalRandomProbe;

    @jdk.internal.vm.annotation.Contended("tlr")
    int threadLocalRandomSecondarySeed;
}
  • threadLocalRandomSeed:用于保存当前线程的主种子。
  • threadLocalRandomSecondarySeed:通常由 ForkJoinPool 等内部使用。

这种实现带来了多项优化:

  • 使用 @Contended 注解避免伪共享(false sharing),通过填充(padding)确保这些变量各自独占缓存行。
  • 使用 sun.misc.Unsafe 直接操作这些字段,避免反射开销。
  • 避免了传统 ThreadLocal 实现中的哈希表查找开销。

6. 结论

本文详细对比了 java.util.Randomjava.util.concurrent.ThreadLocalRandom 的区别。

我们展示了在多线程环境中,ThreadLocalRandom 相较于 Random 的显著性能优势,并演示了如何使用它生成各种类型的随机值。

尽管 ThreadLocalRandom 只是 JDK 中一个简单的新增类,但在高并发应用中,它能带来显著的性能提升