随机数生成是一个非常常见的操作,而且 Java 也提供了 java.util.Random
类用于生成随机数,而且呢,这个类也是线程安全的,就是有一点不好,在多线程下,它的性能不佳。
为什么多线程下,Random 的性能不佳?
因为,它采用了多个线程共享一个 Random 实例。这样就会导致多个线程争用。
为了解决这个问题,Java 7 引入了 java.util.concurrent.ThreadLocalRandom
类,用于在多线程环境中生成随机数。
本文接下来的部分,就来看看如何 ThreadLocalRandom 如何执行以及如何在实际应用程序中使用它。
ThreadLocalRandom Via Random
ThreadLocalRandom 是 ThreadLocal 类和 Random 类的组合,它与当前线程隔离,通过简单地避免对 Random 对象的任何并发访问,在多线程环境中实现了更好的性能。
也就是说,相比于 java.util.Random
类全局的提供随机数生成, 使用 ThreadLocalRandom,一个线程获得的随机数不受另一个线程的影响。
另一个与 Random 类不同的是,ThreadLocalRandom 不支持显式设置种子。因为它重写了从 Random 继承的 setSeed(long seed)
方法,会在调用时始终抛出 UnsupportedOperationException
。
接下来我们看看如何使用 ThreadLocalRandom 生成随机 int
、long
和 double
值。
使用 ThreadLocalRandom 生成随机数
根据 Oracle 文档,我们只需要调用 ThreadLocalRandom.current()
方法,就能返回当前线程的 ThreadLocalRandom
实例。然后,我们可以通过实例的相关方法来生成随机值。
比如下面的代码,生成一个没有任何边界的随机 int 值
int unboundedRandomValue = ThreadLocalRandom.current().nextInt());
其实是有边界的,它的边界就是 int 的边界。
接下来,我们看看如何生成有边界的随机 int 值,这意味着我们需要传递边界下限和边界上限作为参数
int boundedRandomValue = ThreadLocalRandom.current().nextInt(0, 100);
请注意,这是一个左闭右开区间,也就是说,上面的实例生成的随机数在 [0,100)
之间,包含了 0 但不包含 100。
同样的,我们可以通过调用 nextLong()
和 nextDouble()
方法生成 long 和 double 类型的随机值,调用方式与上面示例中 nextInt()
类似。
Java 8 还添加了 nextGaussian()
方法从生成器序列中生成下一个正态分布的值,其值范围在 0.0
和 1.0
之间。
与 Random 方法类似,ThreadLocalRandom 也提供了 doubles()
、ints()
和 longs()
方法生成一序列流式 ( stream ) 的随机值。
使用 JMH 比较 ThreadLocalRandom 和 Random
记下来,我们看看如何在多线程环境中分别使用这两个类生成随机值,然后再使用 JMH 比较它们的性能。
首先,我们创建一个示例,其中所有线程共享一个 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);
上面的代码中,我们把使用 Random 实例生成随机值的任务提交给 ExecutorService 。
然后,我们使用 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
而不是 Random
实例
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 实例。
下面是使用 JMH 对 ThreadLocalRandom 的测试结果
# Run complete. Total time: 00:00:36
Benchmark Mode Cnt Score Error Units
ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom avgt 20 624.911 ± 113.268 us/op
通过 JMH 的测试结果中可以看出,使用 Random 生成 1000 个随机值所花费的平均时间是 772 微秒,但使用 ThreadLocalRandom 只花了 625 微秒。嗯,差距不是很大,但好歹也是有差距的,因为生成 1000 个随机数是瞬间的事情。
因此,我们可以得出结论,ThreadLocalRandom 在高度并发的环境中更有效。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。