从 Java 8 开始,有啥理由写 `new Random()` 吗?
Posted
技术标签:
【中文标题】从 Java 8 开始,有啥理由写 `new Random()` 吗?【英文标题】:Is there any reason to write `new Random()` since Java 8?从 Java 8 开始,有什么理由写 `new Random()` 吗? 【发布时间】:2015-06-24 06:36:38 【问题描述】:出于某种原因,我曾经认为java.util.Random
是线程不安全的,a-la HashMap
或BitSet
,而Math.random()
被实现为使用synchronized
块包装对Random
的访问,或ThreadLocalRandom.current().nextDouble()
。
事实上,java.util.Random
is thread-safe(通过原子)。因此得出结论:即使我需要在单个线程中进行一些随机输入,使用ThreadLocalRandom
也是有意义的,因为内部没有原子读写,编译为锁定指令并发出内存屏障。
此外,由于 Java 8,ThreadLocalRandom
本质上是一个单例,它的状态保存在 java.lang.Thread
类的某些字段中。因此方法ThreadLocalRandom.current()
不是对ThreadLocalMap
的访问,而只是一个静态字段读取,即。 e.很便宜。
我有两个问题:
从计算机科学的角度来看,是几个线性同余随机发生器的输出(以 ThreadLocalRandom
s 的方式初始化)与单个线性同余随机发生器(@ 987654338@实例)?
如果第一个问题的答案是肯定的,是否有任何理由编写构造 new Random()
(无种子)而不是 ThreadLocalRandom.current()
,永远? em>
更新。我认为像ThreadLocalRandom.current().ints().parallel().collect(...)
这样的调用可能不正确,因为线程的随机生成器状态可能未在ForkJoinPool
工作线程中初始化,但似乎ThreadLocalRandom
覆盖了方法ints()
、longs()
和doubles()
,使得以上结构正确。
【问题讨论】:
你不能用 ThreadLocalRandom 写***.com/questions/15182496/…。 @immibis 好吧,这意味着唯一的情况——当我需要重现一些随机数序列时 @immibis 然而,问题是关于具体结构new Random()
,即。 e.没有种子。这个问题是开放的。
【参考方案1】:
1...
这取决于实现,但对于 Java,它会是 same 没有那么糟糕,因为 Java 有一个 static unique seed atomic long that is manipulated everytime 一个随机创建的。但是,在其他语言或实现中我不会感到惊讶,情况并非如此,它们可能只使用系统时间(Java 也使用系统时间,但组合使用唯一种子)。那是在某些系统上,您可以为多个线程获得相同的种子。
经过进一步检查和一些实际测试(尽管是脆弱的测试),看来我之前可能错了,因为在同时(即使它们是不同的实例)。我不完全确定它的种子冲突或实际的全局种子增量是否可以预测的事实。当然,这可能只是我的测试工具或方法。
根据***:
不应信任随机数生成器,尤其是用于并行计算机的随机数生成器。[12]强烈建议使用多个 RNG 检查模拟结果,以检查是否引入了偏差。推荐用于并行计算机的生成器包括使用序列分裂的组合线性同余生成器和使用独立序列的滞后斐波那契生成器。
所以理论上它应该更好,因为 ThreadLocalRandom 会创建独立的序列,所以我的测试可能有缺陷。
这当然是基于伪随机的。
物理随机性或基于实际熵的安全随机生成器可能会导致差异(即更多/更少的熵),但我不是专家,也无权使用。
2...
我无法提出一个特定的用例,但一个可能是您使用一个 ExecutorService 不断创建和处理线程(假设他们无法控制)但一次不会很多(即最多 2并发线程)。您可能会发现 ThreadLocalRandom 比创建单个共享 Random 更昂贵。
考虑到您的 cmets,另一个原因可能更好的原因是您可能想要重置所有进程的种子。如果您有一个使用线程的游戏(不是很多,但让我们假装),您可能希望全局重置种子以进行测试,这比尝试将消息传递给所有正在运行的线程更容易使用 AtomicReference 到 Random。
您可能不想使用 ThreadLocalRandom 的另一个原因是平台原因。一些平台对线程创建和线程本地创建有特定要求。因此,要解决“你有一个比随机数更大的问题”,请查看Google Apps where:
Java 应用程序可以创建一个新线程,但如何创建有一些限制。这些线程不能“超过”创建它们的请求。 (在后端服务器上,应用程序可以生成一个后台线程,该线程可以“超过”创建它的请求。)
为了解决您对为什么要使用不能重用线程的 ExecutorService 的附加评论:
或将 com.google.appengine.api.ThreadManager.currentRequestThreadFactory() 返回的工厂对象与 ExecutorService 一起使用(例如,调用 Executors.newCachedThreadPool(factory))。
即不一定重用线程的线程池。
【讨论】:
2.线程的创建和处置比在该线程内初始化随机生成器要昂贵几个数量级。所以,如果你的 ExecutorService 不断地创建和删除线程(通过 ExecutorServices 的主要目的——reuse 线程),你会遇到比随机数更大的问题... @leventov 我当然同意你的观点。我认为您不了解 ThreadLocalRandom 比 Random 更年轻,并且并非所有运行时都喜欢使用线程局部变量。 ThreadLocal 变量被放入 Java 1.2 版,其中 Random 是 1.0 版。此外,如果您在不同的平台上,您可能无法控制 ExecutorService ... 在某些平台上,您无法控制线程的创建,并且可能会在每次调度时删除线程局部变量。并非一切都是 Java SE 企业! @leventov:Math.random()
的行为已在其文档中指定。将其更改为使用 ThreadLocalRandom
将违反其规范。
@Holger 规范并不是一本写过一次就永远是所有实现的主要来源的圣经。当规范更改为与新的(优化的)实现兼容时,有很多示例,例如Arrays.sort、类初始化锁等
@Holger Java 语言、VM 和 API 规范不能在较小的 JDK 更新中更改(除非存在重大错误)。但是它们在主要版本中发生了变化。例如,Java 7 中重新设计了类加载和初始化过程,并且更新了 JVMS 和 API 以反映这些变化。我的意思是当前的Math.random
规范并不是远离进一步优化的有力理由。可能现在是为 JDK 9 更改此设置的好时机。以上是关于从 Java 8 开始,有啥理由写 `new Random()` 吗?的主要内容,如果未能解决你的问题,请参考以下文章
在 CSS 中对 RGB 颜色值使用十六进制而不是十进制有啥好的理由吗?