java并发系列-介绍 Java 中的 ThreadLocalRandom

1 概览

生成随机数是常见任务,所以 Java 提供了 java.util.Random 类。

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

简单地讲,Random 在多线程环境中性能差的原因在于竞争-多个线程共享同一个 Random 实例。

为了摆脱这个限制,Java 在 JDK 7 中引入了 java.util.concurrent.ThreadLocalRandom 类 - 用于在多线程环境中生成随机数。

下面让我们看看如何在实际应用中使用它。

2 ThreadLocalRandom 超越 Random

ThreadLocalRandom 结合了 ThreadLocalRandom 类,会被隔离在当前线程中。因此,通过简单地避免并发访问 Random 对象,在多线程环境中,它就可以获得更好的性能。

一个线程获取随机数不受其它线程影像,而 java.util.Random 提供的随机数是全局。

同时,和 Random 不同的是,ThreadLocalRandom 不支持设置种子(seed)。它覆盖了继承自RandomsetSeed(long seed) 方法,调用该方法时总是会抛 UnsupportedOperationException

现在我们做一些产生随机 intlongdouble 数的实践。

3 使用 ThreadLocalRandom 生成随机数

根据 Oracle 文档,我们仅需调用 ThreadLocalRandom.current() 方法,就可以拿到当前线程的 ThreadLocalRandom 实例。我们可以通过调用该实例提供的方法生成随机数。

生成随机 int 值,不指定范围:

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

下面,生成指定范围的随机 int 值,该值在给定的最小和最大值之间。

这里的例子生成一个介于 0 和 100 之间的随机 int 值:

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

注意,这里 0 下限是包含的,而 100 上限是不包含的。

我们也可以像上述例子一样调用 nextLong()nextDouble() 方法生成随机 long 值 和 double 值。

Java 8 添加了 nextGaussian() 方法生成正态分布值(normally-distributed),该值来自平均值(mean)为 0.0,标准差为 1.0 的生成序列。

Random 类一样,我们也能使用 doubles()ints()longs() 方法生成随机数流。

4 使用 JMH 比较 ThreadLocalRandomRandom

让我们看看怎样使用这两个类在多线程环境中生成随机数,然后使用 JMH 比较它们的性能。

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

1
2
3
4
5
6
7
8
9
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 基准分析法(benchmaking)检测上述代码的性能:

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

类似地,现在我们使用 ThreadLocalRandom 代替 Random 实例,线程池中的各个线程使用一个 ThreadLocalRandom 实例:

1
2
3
4
5
6
7
8
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);

这里是使用 ThreadLocalRandom 的结果:

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

最后,比较上面 RandomThreadLocalRandom 的 JMH 结果,使用 Random 生成 1000 个随机数的平均时间是 772 微秒,而使用 ThreadLocalRandom 大约为 625 微秒。

因此,我们认为 ThreadLocalRandom 在高并发环境中更高效。

关于 JMH 的更多信息,查看这里

5 总结

这篇文章解释了 java.util.Randomjava.util.concurrent.ThreadLocalRandom 的不同点。

我们也看到了在多线程环境中 ThreadLocalRandomRandom 更先进和性能更高,以及我们如何使用 ThreadLocalRandom 生成随机数。

ThreadLocalRandom 是 JDK 的一个简单添加的功能,但是在高并发应用中有显著作用。


本文为译文,作者通过翻译达到学习目的。 原文链接 | 原文源码链接 | 本站源码链接