Guide to ThreadLocalRandom in Java – Java中的ThreadLocalRandom指南

最后修改: 2018年 1月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Generating random values is a very common task. This is why Java provides the java.util.Random class.

生成随机值是一项非常常见的任务。这就是为什么Java提供了java.util.Random类。

However, this class doesn’t perform well in a multi-threaded environment.

然而,这个类在多线程环境中的表现并不理想。

In a simplified way, the reason for the poor performance of Random in a multi-threaded environment is due to contention – given that multiple threads share the same Random instance.

以一种简化的方式,在多线程环境中,Random的性能不佳的原因是由于争夺–鉴于多个线程共享同一个Random实例。

To address that limitation, Java introduced the java.util.concurrent.ThreadLocalRandom class in JDK 7 – for generating random numbers in a multi-threaded environment.

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

Let’s see how ThreadLocalRandom performs and how to use it in real-world applications.

让我们看看ThreadLocalRandom的表现以及如何在实际应用中使用它。

2. ThreadLocalRandom Over Random

2.ThreadLocalRandom Over Random

ThreadLocalRandom is a combination of the ThreadLocal and Random classes (more on this later) and is isolated to the current thread. Thus, it achieves better performance in a multithreaded environment by simply avoiding any concurrent access to instances of Random.

ThreadLocalRandomThreadLocalRandom类的组合(后面会详细介绍),并且被隔离到当前线程。因此,通过简单地避免对Random实例的任何并发访问,它在多线程环境中实现了更好的性能。

The random number obtained by one thread is not affected by the other thread, whereas java.util.Random provides random numbers globally.

一个线程获得的随机数不会受到其他线程的影响,而java.util.Random则提供全局的随机数。

Also, unlike Random, ThreadLocalRandom doesn’t support setting the seed explicitly. Instead, it overrides the setSeed(long seed) method inherited from Random to always throw an UnsupportedOperationException if called.

此外,与Random不同,ThreadLocalRandom不支持明确地设置种子。相反,它重写了继承自RandomsetSeed(long seed)方法,如果被调用,它总是抛出一个UnsupportedOperationException

2.1. Thread Contention

2.1.线程争夺

So far, we’ve established that the Random class performs poorly in highly concurrent environments. To better understand this, let’s see how one of its primary operations, next(int), is implemented:

到目前为止,我们已经确定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));
}

This is a Java implementation for the Linear Congruential Generator algorithm. It’s obvious that all threads are sharing the same seed instance variable.

这是线性共轭发生器算法的一个Java实现。很明显,所有线程都共享同一个种子实例变量。

To generate the next random set of bits, it first tries to change the shared seed value atomically via compareAndSet or CAS for short.

为了生成下一个随机比特集,它首先尝试通过compareAndSetCAS来原子地改变共享的seed值。

When multiple threads attempt to update the seed concurrently using CAS, one thread wins and updates the seed, and the rest lose. Losing threads will try the same process over and over again until they get a chance to update the value and ultimately generate the random number.

当多个线程试图使用CAS同时更新种子时,一个线程获胜并更新了种子,而其他线程则失败。失败的线程将一次又一次地尝试同样的过程,直到他们有机会更新值,并最终生成随机数。

This algorithm is lock-free, and different threads can progress concurrently. However, when the contention is high, the number of CAS failures and retries will hurt the overall performance significantly.

这种算法是无锁的,不同的线程可以并发地进行。然而,当竞争很激烈时,CAS失败和重试的数量将大大损害整体性能。

On the other hand, the ThreadLocalRandom completely removes this contention, as each thread has its own instance of Random and, consequently, its own confined seed.

另一方面,ThreadLocalRandom完全消除了这种争论,因为每个线程都有自己的Random实例,因此也有自己的封闭的种子。

Let’s now take a look at some of the ways to generate random int, long and double values.

现在让我们来看看生成随机int、longdouble值的一些方法。

3. Generating Random Values Using ThreadLocalRandom

3.使用ThreadLocalRandom生成随机值

As per the Oracle documentation, we just need to call ThreadLocalRandom.current() method, and it will return the instance of ThreadLocalRandom for the current thread. We can then generate random values by invoking available instance methods of the class.

根据Oracle文档,我们只需要调用ThreadLocalRandom.current()方法,它将返回当前线程的ThreadLocalRandom的实例。然后我们可以通过调用该类的可用实例方法来生成随机值。

Let’s generate a random int value without any bounds:

让我们生成一个没有任何界限的随机int值。

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

Next, let’s see how we can generate a random bounded int value, meaning a value between a given lower and upper limit.

接下来,让我们看看如何生成一个随机的有界int值,即一个介于给定下限和上限之间的值。

Here’s an example of generating a random int value between 0 and 100:

下面是一个生成0到100之间随机int值的例子:

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

Please note, 0 is the inclusive lower limit and 100 is the exclusive upper limit.

请注意,0是包容性下限,100是排他性上限。

We can generate random values for long and double by invoking nextLong() and nextDouble() methods in a similar way as shown in the examples above.

我们可以通过调用nextLong()nextDouble()方法,为longdouble生成随机值,方法类似于上面的例子中所示。

Java 8 also adds the nextGaussian() method to generate the next normally-distributed value with a 0.0 mean and 1.0 standard deviation from the generator’s sequence.

Java 8还增加了nextGaussian()方法,用于从生成器的序列中生成下一个平均值为0.0、标准差为1.0的正态分布值。

As with the Random class, we can also use the doubles(), ints() and longs() methods to generate streams of random values.

Random类一样,我们也可以使用doubles()、ints()longs()方法来生成随机值的流。

4. Comparing ThreadLocalRandom and Random Using JMH

4.使用JMH对ThreadLocalRandomRandom进行比较

Let’s see how we can generate random values in a multi-threaded environment, by using the two classes, then compare their performance using JMH.

让我们看看如何在多线程环境下,通过使用这两个类来生成随机值,然后用JMH来比较它们的性能。

First, let’s create an example where all the threads are sharing a single instance of Random. Here, we’re submitting the task of generating a random value using the Random instance to an ExecutorService:

首先,让我们创建一个例子,所有线程都共享一个Random.实例。在这里,我们将使用Random实例生成一个随机值的任务提交给ExecutorService:

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

Let’s check the performance of the code above using JMH benchmarking:

让我们使用JMH基准测试来检查上述代码的性能。

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

Similarly, let’s now use ThreadLocalRandom instead of the Random instance, which uses one instance of ThreadLocalRandom for each thread in the pool:

同样,我们现在使用ThreadLocalRandom代替Random实例,它为池中的每个线程使用一个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);

Here’s the result of using ThreadLocalRandom:

下面是使用ThreadLocalRandom的结果:

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

Finally, by comparing the JMH results above for both Random and ThreadLocalRandom, we can clearly see that the average time taken to generate 1000 random values using Random is 772 microseconds, whereas using ThreadLocalRandom it’s around 625 microseconds.

最后,通过比较上面RandomThreadLocalRandom的JMH结果,我们可以清楚地看到,使用Random生成1000个随机值的平均时间是772微秒,而使用ThreadLocalRandom则是大约625微秒。

Thus, we can conclude that ThreadLocalRandom is more efficient in a highly concurrent environment.

因此,我们可以得出结论:ThreadLocalRandom在高并发环境下更有效率

To learn more about JMH, check out our previous article here.

要了解有关JMH的更多信息,请查看我们之前的文章

5. Implementation Details

5.实施细节

It’s a good mental model to think of a ThreadLocalRandom as a combination of ThreadLocal and Random classes. As a matter of fact, this mental model was aligned with the actual implementation before Java 8.

ThreadLocalRandom视为ThreadLocalRandom类的组合,是一个很好的心理模型。事实上,这种心理模型与Java 8之前的实际实现是一致的。

As of Java 8, however, this alignment broke down completely as the ThreadLocalRandom became a singleton. Here’s how the current() method looks in Java 8+:

然而,从Java 8开始,由于ThreadLocalRandom变成了一个单例,这种排列方式完全被打破了。下面是current()方法在Java 8+中的样子。

static final ThreadLocalRandom instance = new ThreadLocalRandom();

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

    return instance;
}

It’s true that sharing one global Random instance leads to sub-optimal performance in high contention. However, using one dedicated instance per thread is also overkill.

的确,共享一个全局Random实例会导致在高争论中的次优性能。然而,每个线程使用一个专用实例也是矫枉过正。

Instead of a dedicated instance of Random per thread, each thread only needs to maintain its own seed value. As of Java 8, the Thread class itself has been retrofitted to maintain the seed value:

每个线程不需要一个专门的Random实例,每个线程只需要维护自己的seed 。从 Java 8 开始,线程类本身已被改造为维护seed 值。

public class Thread implements Runnable {
    // omitted

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

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

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

The threadLocalRandomSeed variable is responsible for maintaining the current seed value for ThreadLocalRandom. Moreover, the secondary seed, threadLocalRandomSecondarySeed, is usually used internally by the likes of ForkJoinPool.

threadLocalRandomSeed变量负责维护ThreadLocalRandom的当前种子值。threadLocalRandomSecondarySeed,通常由ForkJoinPool.等内部使用。

This implementation incorporates a few optimizations to make ThreadLocalRandom even more performant:

这个实现包含了一些优化,以使ThreadLocalRandom的性能更强。

  • Avoiding false sharing by using the @Contented annotation, which basically adds enough padding to isolate the contended variables in their own cache lines
  • Using sun.misc.Unsafe to update these three variables instead of using the Reflection API
  • Avoiding extra hashtable lookups associated with the ThreadLocal implementation

6. Conclusion

6.结论

This article illustrated the difference between java.util.Random and java.util.concurrent.ThreadLocalRandom.

本文说明了java.util.Randomjava.util.concurrent.ThreadLocalRandom的区别。

We also saw the advantage of ThreadLocalRandom over Random in a multithreaded environment, as well as performance and how we can generate random values using the class.

我们还看到了ThreadLocalRandomRandom在多线程环境中的优势,以及性能和我们如何使用该类产生随机值。

ThreadLocalRandom is a simple addition to the JDK, but it can create a notable impact in highly concurrent applications.

ThreadLocalRandom是对JDK的一个简单补充,但它可以在高并发的应用程序中产生显著的影响。

And, as always, the implementation of all of these examples can be found over on GitHub.

而且,像往常一样,所有这些例子的实现都可以在GitHub上找到